Replace `ImageHorzList` 🤮 with `MediaPreview` ✨ (#5143)
parent
82ca0b16b6
commit
5f5c14d044
|
@ -0,0 +1,169 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
||||||
|
import {Image} from 'expo-image'
|
||||||
|
import {
|
||||||
|
AppBskyEmbedExternal,
|
||||||
|
AppBskyEmbedImages,
|
||||||
|
AppBskyEmbedRecordWithMedia,
|
||||||
|
AppBskyEmbedVideo,
|
||||||
|
} from '@atproto/api'
|
||||||
|
import {Trans} from '@lingui/macro'
|
||||||
|
|
||||||
|
import {parseTenorGif} from '#/lib/strings/embed-player'
|
||||||
|
import {atoms as a} from '#/alf'
|
||||||
|
import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streamlined MediaPreview component which just handles images, gifs, and videos
|
||||||
|
*/
|
||||||
|
export function Embed({
|
||||||
|
embed,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
embed?:
|
||||||
|
| AppBskyEmbedImages.View
|
||||||
|
| AppBskyEmbedRecordWithMedia.View
|
||||||
|
| AppBskyEmbedExternal.View
|
||||||
|
| AppBskyEmbedVideo.View
|
||||||
|
| {[k: string]: unknown}
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
}) {
|
||||||
|
let media = AppBskyEmbedRecordWithMedia.isView(embed) ? embed.media : embed
|
||||||
|
|
||||||
|
if (AppBskyEmbedImages.isView(media)) {
|
||||||
|
return (
|
||||||
|
<Outer style={style}>
|
||||||
|
{media.images.map(image => (
|
||||||
|
<ImageItem
|
||||||
|
key={image.thumb}
|
||||||
|
thumbnail={image.thumb}
|
||||||
|
alt={image.alt}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Outer>
|
||||||
|
)
|
||||||
|
} else if (AppBskyEmbedExternal.isView(embed) && embed.external.thumb) {
|
||||||
|
let url: URL | undefined
|
||||||
|
try {
|
||||||
|
url = new URL(embed.external.uri)
|
||||||
|
} catch {}
|
||||||
|
if (url) {
|
||||||
|
const {success} = parseTenorGif(url)
|
||||||
|
if (success) {
|
||||||
|
return (
|
||||||
|
<Outer style={style}>
|
||||||
|
<GifItem
|
||||||
|
thumbnail={embed.external.thumb}
|
||||||
|
alt={embed.external.title}
|
||||||
|
/>
|
||||||
|
</Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (AppBskyEmbedVideo.isView(embed)) {
|
||||||
|
return (
|
||||||
|
<Outer style={style}>
|
||||||
|
<VideoItem thumbnail={embed.thumbnail} alt={embed.alt} />
|
||||||
|
</Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Outer({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
children?: React.ReactNode
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
}) {
|
||||||
|
return <View style={[a.flex_row, a.gap_xs, style]}>{children}</View>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ImageItem({
|
||||||
|
thumbnail,
|
||||||
|
alt,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
thumbnail: string
|
||||||
|
alt?: string
|
||||||
|
children?: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<View style={[a.relative, a.flex_1, {aspectRatio: 1, maxWidth: 100}]}>
|
||||||
|
<Image
|
||||||
|
key={thumbnail}
|
||||||
|
source={{uri: thumbnail}}
|
||||||
|
style={[a.flex_1, a.rounded_xs]}
|
||||||
|
contentFit="cover"
|
||||||
|
accessible={true}
|
||||||
|
accessibilityIgnoresInvertColors
|
||||||
|
accessibilityHint={alt}
|
||||||
|
accessibilityLabel=""
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GifItem({thumbnail, alt}: {thumbnail: string; alt?: string}) {
|
||||||
|
return (
|
||||||
|
<ImageItem thumbnail={thumbnail} alt={alt}>
|
||||||
|
<View style={styles.altContainer}>
|
||||||
|
<Text style={styles.alt}>
|
||||||
|
<Trans>GIF</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</ImageItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VideoItem({
|
||||||
|
thumbnail,
|
||||||
|
alt,
|
||||||
|
}: {
|
||||||
|
thumbnail?: string
|
||||||
|
alt?: string
|
||||||
|
}) {
|
||||||
|
if (!thumbnail) {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
{backgroundColor: 'black'},
|
||||||
|
a.flex_1,
|
||||||
|
{aspectRatio: 1, maxWidth: 100},
|
||||||
|
a.justify_center,
|
||||||
|
a.align_center,
|
||||||
|
]}>
|
||||||
|
<PlayIcon size="xl" fill="white" />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ImageItem thumbnail={thumbnail} alt={alt}>
|
||||||
|
<View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}>
|
||||||
|
<PlayIcon size="xl" fill="white" />
|
||||||
|
</View>
|
||||||
|
</ImageItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
altContainer: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
||||||
|
borderRadius: 6,
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
paddingVertical: 3,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 5,
|
||||||
|
bottom: 5,
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
alt: {
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
})
|
|
@ -1,8 +1,6 @@
|
||||||
import React, {useCallback, useEffect, useMemo, useState} from 'react'
|
import React, {useCallback, useEffect, useMemo, useState} from 'react'
|
||||||
import {LayoutAnimation, View} from 'react-native'
|
import {LayoutAnimation, View} from 'react-native'
|
||||||
import {
|
import {
|
||||||
AppBskyEmbedImages,
|
|
||||||
AppBskyEmbedRecordWithMedia,
|
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AppBskyRichtextFacet,
|
AppBskyRichtextFacet,
|
||||||
AtUri,
|
AtUri,
|
||||||
|
@ -22,12 +20,12 @@ import {
|
||||||
} from '#/lib/strings/url-helpers'
|
} from '#/lib/strings/url-helpers'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {usePostQuery} from '#/state/queries/post'
|
import {usePostQuery} from '#/state/queries/post'
|
||||||
import {ImageHorzList} from '#/view/com/util/images/ImageHorzList'
|
|
||||||
import {PostMeta} from '#/view/com/util/PostMeta'
|
import {PostMeta} from '#/view/com/util/PostMeta'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import * as MediaPreview from '#/components/MediaPreview'
|
||||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
|
@ -160,13 +158,6 @@ export function MessageInputEmbed({
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const images = AppBskyEmbedImages.isView(post.embed)
|
|
||||||
? post.embed.images
|
|
||||||
: AppBskyEmbedRecordWithMedia.isView(post.embed) &&
|
|
||||||
AppBskyEmbedImages.isView(post.embed.media)
|
|
||||||
? post.embed.media.images
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
@ -202,9 +193,7 @@ export function MessageInputEmbed({
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{images && images?.length > 0 && (
|
<MediaPreview.Embed embed={post.embed} style={a.mt_sm} />
|
||||||
<ImageHorzList images={images} style={a.mt_xs} />
|
|
||||||
)}
|
|
||||||
</ContentHider>
|
</ContentHider>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,9 +8,6 @@ import {
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {
|
import {
|
||||||
AppBskyActorDefs,
|
AppBskyActorDefs,
|
||||||
AppBskyEmbedExternal,
|
|
||||||
AppBskyEmbedImages,
|
|
||||||
AppBskyEmbedRecordWithMedia,
|
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AppBskyGraphFollow,
|
AppBskyGraphFollow,
|
||||||
|
@ -25,7 +22,6 @@ import {useLingui} from '@lingui/react'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {parseTenorGif} from '#/lib/strings/embed-player'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {FeedNotification} from '#/state/queries/notifications/feed'
|
import {FeedNotification} from '#/state/queries/notifications/feed'
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||||
|
@ -52,11 +48,11 @@ import {PersonPlus_Filled_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/com
|
||||||
import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
|
import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
|
||||||
import {StarterPack} from '#/components/icons/StarterPack'
|
import {StarterPack} from '#/components/icons/StarterPack'
|
||||||
import {Link as NewLink} from '#/components/Link'
|
import {Link as NewLink} from '#/components/Link'
|
||||||
|
import * as MediaPreview from '#/components/MediaPreview'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
||||||
import {FeedSourceCard} from '../feeds/FeedSourceCard'
|
import {FeedSourceCard} from '../feeds/FeedSourceCard'
|
||||||
import {Post} from '../post/Post'
|
import {Post} from '../post/Post'
|
||||||
import {ImageHorzList} from '../util/images/ImageHorzList'
|
|
||||||
import {Link, TextLink} from '../util/Link'
|
import {Link, TextLink} from '../util/Link'
|
||||||
import {formatCount} from '../util/numeric/format'
|
import {formatCount} from '../util/numeric/format'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
|
@ -593,49 +589,14 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
if (post && AppBskyFeedPost.isRecord(post?.record)) {
|
if (post && AppBskyFeedPost.isRecord(post?.record)) {
|
||||||
const text = post.record.text
|
const text = post.record.text
|
||||||
let images
|
|
||||||
let isGif = false
|
|
||||||
|
|
||||||
if (AppBskyEmbedImages.isView(post.embed)) {
|
|
||||||
images = post.embed.images
|
|
||||||
} else if (
|
|
||||||
AppBskyEmbedRecordWithMedia.isView(post.embed) &&
|
|
||||||
AppBskyEmbedImages.isView(post.embed.media)
|
|
||||||
) {
|
|
||||||
images = post.embed.media.images
|
|
||||||
} else if (
|
|
||||||
AppBskyEmbedExternal.isView(post.embed) &&
|
|
||||||
post.embed.external.thumb
|
|
||||||
) {
|
|
||||||
let url: URL | undefined
|
|
||||||
try {
|
|
||||||
url = new URL(post.embed.external.uri)
|
|
||||||
} catch {}
|
|
||||||
if (url) {
|
|
||||||
const {success} = parseTenorGif(url)
|
|
||||||
if (success) {
|
|
||||||
isGif = true
|
|
||||||
images = [
|
|
||||||
{
|
|
||||||
thumb: post.embed.external.thumb,
|
|
||||||
alt: post.embed.external.title,
|
|
||||||
fullsize: post.embed.external.thumb,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{text?.length > 0 && <Text style={pal.textLight}>{text}</Text>}
|
{text?.length > 0 && <Text style={pal.textLight}>{text}</Text>}
|
||||||
{images && images.length > 0 && (
|
<MediaPreview.Embed
|
||||||
<ImageHorzList
|
embed={post.embed}
|
||||||
images={images}
|
style={styles.additionalPostImages}
|
||||||
style={styles.additionalPostImages}
|
/>
|
||||||
gif={isGif}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
|
||||||
import {Image} from 'expo-image'
|
|
||||||
import {AppBskyEmbedImages} from '@atproto/api'
|
|
||||||
import {Trans} from '@lingui/macro'
|
|
||||||
|
|
||||||
import {atoms as a} from '#/alf'
|
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
images: AppBskyEmbedImages.ViewImage[]
|
|
||||||
style?: StyleProp<ViewStyle>
|
|
||||||
gif?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ImageHorzList({images, style, gif}: Props) {
|
|
||||||
return (
|
|
||||||
<View style={[a.flex_row, a.gap_xs, style]}>
|
|
||||||
{images.map(({thumb, alt}) => (
|
|
||||||
<View
|
|
||||||
key={thumb}
|
|
||||||
style={[a.relative, a.flex_1, {aspectRatio: 1, maxWidth: 100}]}>
|
|
||||||
<Image
|
|
||||||
key={thumb}
|
|
||||||
source={{uri: thumb}}
|
|
||||||
style={[a.flex_1, a.rounded_xs]}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityIgnoresInvertColors
|
|
||||||
accessibilityHint={alt}
|
|
||||||
accessibilityLabel=""
|
|
||||||
/>
|
|
||||||
{gif && (
|
|
||||||
<View style={styles.altContainer}>
|
|
||||||
<Text style={styles.alt}>
|
|
||||||
<Trans>GIF</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
altContainer: {
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
|
||||||
borderRadius: 6,
|
|
||||||
paddingHorizontal: 6,
|
|
||||||
paddingVertical: 3,
|
|
||||||
position: 'absolute',
|
|
||||||
right: 5,
|
|
||||||
bottom: 5,
|
|
||||||
zIndex: 2,
|
|
||||||
},
|
|
||||||
alt: {
|
|
||||||
color: 'white',
|
|
||||||
fontSize: 7,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
})
|
|
Loading…
Reference in New Issue