diff options
author | Samuel Newman <mozzius@protonmail.com> | 2024-09-04 18:52:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-04 10:52:41 -0700 |
commit | 5f5c14d044042e25b78bd173d245d5790843ff85 (patch) | |
tree | 76b4dabc0ef11172e07e71865643de0b94982a06 /src | |
parent | 82ca0b16b6385f727e1d305dc48256bc45a0086d (diff) | |
download | voidsky-5f5c14d044042e25b78bd173d245d5790843ff85.tar.zst |
Replace `ImageHorzList` 🤮 with `MediaPreview` ✨ (#5143)
Diffstat (limited to 'src')
-rw-r--r-- | src/components/MediaPreview.tsx | 169 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/MessageInputEmbed.tsx | 15 | ||||
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 49 | ||||
-rw-r--r-- | src/view/com/util/images/ImageHorzList.tsx | 61 |
4 files changed, 176 insertions, 118 deletions
diff --git a/src/components/MediaPreview.tsx b/src/components/MediaPreview.tsx new file mode 100644 index 000000000..17bae55b5 --- /dev/null +++ b/src/components/MediaPreview.tsx @@ -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', + }, +}) diff --git a/src/screens/Messages/Conversation/MessageInputEmbed.tsx b/src/screens/Messages/Conversation/MessageInputEmbed.tsx index 4fdd31bcf..bf28ed4fe 100644 --- a/src/screens/Messages/Conversation/MessageInputEmbed.tsx +++ b/src/screens/Messages/Conversation/MessageInputEmbed.tsx @@ -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> ) diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 3e8f8d86d..b1cf3b485 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -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} - style={styles.additionalPostImages} - gif={isGif} - /> - )} + <MediaPreview.Embed + embed={post.embed} + style={styles.additionalPostImages} + /> </> ) } diff --git a/src/view/com/util/images/ImageHorzList.tsx b/src/view/com/util/images/ImageHorzList.tsx deleted file mode 100644 index bade2a444..000000000 --- a/src/view/com/util/images/ImageHorzList.tsx +++ /dev/null @@ -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', - }, -}) |