From 9a681a22efc93a0def7c02407f2d9c040001dea7 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 13 Jan 2026 17:21:38 +0200 Subject: [PATCH] Use real image embed components for draft preview Replace custom image preview with AutoSizedImage for single images and ImageLayoutGrid for multiple images. This gives drafts the same polished image display as regular posts. Co-Authored-By: Claude Opus 4.5 --- src/view/com/composer/drafts/DraftItem.tsx | 96 +++++++++++++--------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/src/view/com/composer/drafts/DraftItem.tsx b/src/view/com/composer/drafts/DraftItem.tsx index 474e572b06..b394b87a86 100644 --- a/src/view/com/composer/drafts/DraftItem.tsx +++ b/src/view/com/composer/drafts/DraftItem.tsx @@ -1,12 +1,20 @@ -import {useCallback, useEffect, useState} from 'react' -import {Image, Pressable, View} from 'react-native' +import {useCallback, useEffect, useMemo, useState} from 'react' +import {Pressable, View} from 'react-native' +import {Image} from 'expo-image' +import {type AppBskyEmbedImages} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {isNative} from '#/platform/detection' -import {type DraftPostDisplay, type DraftSummary} from '#/state/drafts' +import { + type DraftPostDisplay, + type DraftSummary, + type LocalMediaRef, +} from '#/state/drafts' import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile' import {useSession} from '#/state/session' +import {AutoSizedImage} from '#/view/com/util/images/AutoSizedImage' +import {ImageLayoutGrid} from '#/view/com/util/images/ImageLayoutGrid' import {TimeElapsed} from '#/view/com/util/TimeElapsed' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' @@ -134,13 +142,13 @@ function DraftPostRow({ {/* Content column */} - + {/* Header row: name, handle, timestamp, menu */} {displayName && ( {displayName} @@ -197,10 +205,15 @@ function DraftPostRow({ ) } +type LoadedImage = { + url: string + meta: LocalMediaRef +} + function DraftMediaPreview({post}: {post: DraftPostDisplay}) { const t = useTheme() const {currentAccount} = useSession() - const [imageUrls, setImageUrls] = useState([]) + const [loadedImages, setLoadedImages] = useState([]) const [gifUrl, setGifUrl] = useState(null) useEffect(() => { @@ -209,20 +222,20 @@ function DraftMediaPreview({post}: {post: DraftPostDisplay}) { // Load images if (post.images && post.images.length > 0) { - const urls: string[] = [] + const loaded: LoadedImage[] = [] for (const image of post.images) { try { const url = await storage.loadMediaFromLocal( currentAccount.did, image.localId, ) - urls.push(url) + loaded.push({url, meta: image}) } catch (e) { // Image might not exist anymore console.warn('Failed to load draft image', e) } } - setImageUrls(urls) + setLoadedImages(loaded) } // GIFs have a URL directly @@ -234,52 +247,50 @@ function DraftMediaPreview({post}: {post: DraftPostDisplay}) { loadMedia() }, [currentAccount?.did, post.images, post.gif]) + // Convert loaded images to ViewImage format for the embed components + const viewImages = useMemo(() => { + return loadedImages.map(({url, meta}) => ({ + thumb: url, + fullsize: url, + alt: meta.altText || '', + aspectRatio: + meta.width && meta.height + ? {width: meta.width, height: meta.height} + : undefined, + })) + }, [loadedImages]) + // Nothing to show - if (imageUrls.length === 0 && !gifUrl && !post.video) { + if (viewImages.length === 0 && !gifUrl && !post.video) { return null } return ( - - {/* Images grid */} - {imageUrls.length > 0 && ( - - {imageUrls.map((url, index) => ( - - - - ))} - + + {/* Images - use real embed components */} + {viewImages.length === 1 && ( + )} + {viewImages.length > 1 && } {/* GIF preview */} {gifUrl && ( @@ -289,12 +300,17 @@ function DraftMediaPreview({post}: {post: DraftPostDisplay}) { {post.video && ( Video attached