diff --git a/src/components/MediaPreview.tsx b/src/components/MediaPreview.tsx new file mode 100644 index 00000000..17bae55b --- /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 +}) { + let media = AppBskyEmbedRecordWithMedia.isView(embed) ? embed.media : embed + + if (AppBskyEmbedImages.isView(media)) { + return ( + + {media.images.map(image => ( + + ))} + + ) + } 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 ( + + + + ) + } + } + } else if (AppBskyEmbedVideo.isView(embed)) { + return ( + + + + ) + } + + return null +} + +export function Outer({ + children, + style, +}: { + children?: React.ReactNode + style?: StyleProp +}) { + return {children} +} + +export function ImageItem({ + thumbnail, + alt, + children, +}: { + thumbnail: string + alt?: string + children?: React.ReactNode +}) { + return ( + + + {children} + + ) +} + +export function GifItem({thumbnail, alt}: {thumbnail: string; alt?: string}) { + return ( + + + + GIF + + + + ) +} + +export function VideoItem({ + thumbnail, + alt, +}: { + thumbnail?: string + alt?: string +}) { + if (!thumbnail) { + return ( + + + + ) + } + return ( + + + + + + ) +} + +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 4fdd31bc..bf28ed4f 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 = ( )} - {images && images?.length > 0 && ( - - )} + ) diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 3e8f8d86..b1cf3b48 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}} - {images && images.length > 0 && ( - - )} + ) } diff --git a/src/view/com/util/images/ImageHorzList.tsx b/src/view/com/util/images/ImageHorzList.tsx deleted file mode 100644 index bade2a44..00000000 --- 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 - gif?: boolean -} - -export function ImageHorzList({images, style, gif}: Props) { - return ( - - {images.map(({thumb, alt}) => ( - - - {gif && ( - - - GIF - - - )} - - ))} - - ) -} - -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', - }, -})