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 {LayoutAnimation, View} from 'react-native'
|
||||
import {
|
||||
AppBskyEmbedImages,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedPost,
|
||||
AppBskyRichtextFacet,
|
||||
AtUri,
|
||||
|
@ -22,12 +20,12 @@ import {
|
|||
} from '#/lib/strings/url-helpers'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {usePostQuery} from '#/state/queries/post'
|
||||
import {ImageHorzList} from '#/view/com/util/images/ImageHorzList'
|
||||
import {PostMeta} from '#/view/com/util/PostMeta'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as MediaPreview from '#/components/MediaPreview'
|
||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||
import {RichText} from '#/components/RichText'
|
||||
|
@ -160,13 +158,6 @@ export function MessageInputEmbed({
|
|||
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 = (
|
||||
<View
|
||||
style={[
|
||||
|
@ -202,9 +193,7 @@ export function MessageInputEmbed({
|
|||
/>
|
||||
</View>
|
||||
)}
|
||||
{images && images?.length > 0 && (
|
||||
<ImageHorzList images={images} style={a.mt_xs} />
|
||||
)}
|
||||
<MediaPreview.Embed embed={post.embed} style={a.mt_sm} />
|
||||
</ContentHider>
|
||||
</View>
|
||||
)
|
||||
|
|
|
@ -8,9 +8,6 @@ import {
|
|||
} from 'react-native'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyEmbedExternal,
|
||||
AppBskyEmbedImages,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AppBskyGraphFollow,
|
||||
|
@ -25,7 +22,6 @@ import {useLingui} from '@lingui/react'
|
|||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {parseTenorGif} from '#/lib/strings/embed-player'
|
||||
import {logger} from '#/logger'
|
||||
import {FeedNotification} from '#/state/queries/notifications/feed'
|
||||
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 {StarterPack} from '#/components/icons/StarterPack'
|
||||
import {Link as NewLink} from '#/components/Link'
|
||||
import * as MediaPreview from '#/components/MediaPreview'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
||||
import {FeedSourceCard} from '../feeds/FeedSourceCard'
|
||||
import {Post} from '../post/Post'
|
||||
import {ImageHorzList} from '../util/images/ImageHorzList'
|
||||
import {Link, TextLink} from '../util/Link'
|
||||
import {formatCount} from '../util/numeric/format'
|
||||
import {Text} from '../util/text/Text'
|
||||
|
@ -593,49 +589,14 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
|
|||
const pal = usePalette('default')
|
||||
if (post && AppBskyFeedPost.isRecord(post?.record)) {
|
||||
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 (
|
||||
<>
|
||||
{text?.length > 0 && <Text style={pal.textLight}>{text}</Text>}
|
||||
{images && images.length > 0 && (
|
||||
<ImageHorzList
|
||||
images={images}
|
||||
<MediaPreview.Embed
|
||||
embed={post.embed}
|
||||
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