add peek menu to media preview

This commit is contained in:
Samuel Newman
2026-04-20 14:28:40 +03:00
parent 91bb4f45e6
commit afcb8f4fcb
2 changed files with 61 additions and 10 deletions
+60 -10
View File
@@ -1,11 +1,16 @@
import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {type AppBskyFeedDefs} from '@atproto/api' import {type AppBskyEmbedImages, type AppBskyFeedDefs} from '@atproto/api'
import {Trans} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {shareImageModal} from '#/lib/media/manip'
import {useSaveImageToMediaLibrary} from '#/lib/media/save-image'
import {isGifEmbed} from '#/lib/strings/embed-player' import {isGifEmbed} from '#/lib/strings/embed-player'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, tokens, useTheme} from '#/alf'
import {ArrowShareRight_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowShareRight'
import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download'
import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import * as PeekMenu from '#/components/PeekMenu'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
@@ -16,9 +21,11 @@ import * as bsky from '#/types/bsky'
export function Embed({ export function Embed({
embed, embed,
style, style,
peekable = false,
}: { }: {
embed: AppBskyFeedDefs.PostView['embed'] embed: AppBskyFeedDefs.PostView['embed']
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
peekable?: boolean
}) { }) {
const e = bsky.post.parseEmbed(embed) const e = bsky.post.parseEmbed(embed)
@@ -27,13 +34,17 @@ export function Embed({
if (e.type === 'images') { if (e.type === 'images') {
return ( return (
<Outer style={style}> <Outer style={style}>
{e.view.images.map(image => ( {e.view.images.map(image =>
<ImageItem peekable ? (
key={image.thumb} <PeekableImageItem key={image.thumb} image={image} />
thumbnail={image.thumb} ) : (
alt={image.alt} <ImageItem
/> key={image.thumb}
))} thumbnail={image.thumb}
alt={image.alt}
/>
),
)}
</Outer> </Outer>
) )
} else if (e.type === 'link') { } else if (e.type === 'link') {
@@ -156,6 +167,45 @@ export function VideoItem({
) )
} }
function PeekableImageItem({image}: {image: AppBskyEmbedImages.ViewImage}) {
const {t: l} = useLingui()
const saveImage = useSaveImageToMediaLibrary()
const aspect =
image.aspectRatio && image.aspectRatio.height > 0
? image.aspectRatio.width / image.aspectRatio.height
: undefined
return (
<PeekMenu.Root style={[a.flex_1, {maxWidth: 100}]}>
<PeekMenu.Trigger
preview={{
type: 'image',
uri: image.fullsize,
thumbUri: image.thumb,
aspectRatio: aspect && aspect > 0 ? aspect : 1,
}}
borderRadius={tokens.borderRadius.xs}>
<ImageItem thumbnail={image.thumb} alt={image.alt} />
</PeekMenu.Trigger>
<PeekMenu.Menu>
<PeekMenu.MenuItem
id="save"
onSelect={() => void saveImage(image.fullsize)}>
<PeekMenu.MenuItemIcon icon={DownloadIcon} />
<PeekMenu.MenuItemText>{l`Save image`}</PeekMenu.MenuItemText>
</PeekMenu.MenuItem>
<PeekMenu.MenuItem
id="share"
onSelect={() => void shareImageModal({uri: image.fullsize})}>
<PeekMenu.MenuItemIcon icon={ShareIcon} />
<PeekMenu.MenuItemText>{l`Share`}</PeekMenu.MenuItemText>
</PeekMenu.MenuItem>
</PeekMenu.Menu>
</PeekMenu.Root>
)
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
altContainer: { altContainer: {
backgroundColor: 'rgba(0, 0, 0, 0.75)', backgroundColor: 'rgba(0, 0, 0, 0.75)',
@@ -1093,6 +1093,7 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
<MediaPreview.Embed <MediaPreview.Embed
embed={post.embed} embed={post.embed}
style={styles.additionalPostImages} style={styles.additionalPostImages}
peekable
/> />
</> </>
) )