Use minimal media mode for draft display - REVERT IF NEEDED

This commit is contained in:
Samuel Newman
2026-01-16 21:52:42 +02:00
parent 11aee73940
commit 834632ce1c
2 changed files with 31 additions and 98 deletions
+29 -98
View File
@@ -1,7 +1,5 @@
import {useCallback, useEffect, useMemo, useState} from 'react' import {useCallback, useEffect, useState} from 'react'
import {Pressable, View} from 'react-native' import {Pressable, View} from 'react-native'
import {Image} from 'expo-image'
import {type AppBskyEmbedImages} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -12,15 +10,10 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
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 {DotGrid_Stroke2_Corner0_Rounded as DotsIcon} from '#/components/icons/DotGrid' import {DotGrid_Stroke2_Corner0_Rounded as DotsIcon} from '#/components/icons/DotGrid'
import {AutoSizedImage} from '#/components/images/AutoSizedImage' import * as MediaPreview from '#/components/MediaPreview'
import {ImageLayoutGrid} from '#/components/images/ImageLayoutGrid'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import { import {type DraftPostDisplay, type DraftSummary} from './state/schema'
type DraftPostDisplay,
type DraftSummary,
type LocalMediaDisplay,
} from './state/schema'
import * as storage from './state/storage' import * as storage from './state/storage'
export function DraftItem({ export function DraftItem({
@@ -217,124 +210,62 @@ function DraftPostRow({
type LoadedImage = { type LoadedImage = {
url: string url: string
meta: LocalMediaDisplay alt: string
width?: number
height?: number
} }
function DraftMediaPreview({post}: {post: DraftPostDisplay}) { function DraftMediaPreview({post}: {post: DraftPostDisplay}) {
const t = useTheme()
const [loadedImages, setLoadedImages] = useState<LoadedImage[]>([]) const [loadedImages, setLoadedImages] = useState<LoadedImage[]>([])
const [videoThumbnail, setVideoThumbnail] = useState<string | undefined>()
useEffect(() => { useEffect(() => {
async function loadMedia() { async function loadMedia() {
// Try to load all images - the exists flag may be stale due to async cache // Load images
if (post.images && post.images.length > 0) { if (post.images && post.images.length > 0) {
const loaded: LoadedImage[] = [] const loaded: LoadedImage[] = []
for (const image of post.images) { for (const image of post.images) {
try { try {
const url = await storage.loadMediaFromLocal(image.localPath) const url = await storage.loadMediaFromLocal(image.localPath)
// Get dimensions using expo-image's loadAsync loaded.push({url, alt: image.altText || ''})
let width: number | undefined
let height: number | undefined
try {
const imageRef = await Image.loadAsync(url)
width = imageRef.width
height = imageRef.height
} catch {
// Dimensions unavailable, will use default aspect ratio
}
loaded.push({url, meta: {...image, exists: true}, width, height})
} catch (e) { } catch (e) {
// Image doesn't exist locally, skip it // Image doesn't exist locally, skip it
} }
} }
setLoadedImages(loaded) setLoadedImages(loaded)
} }
// Load video thumbnail
if (post.video?.exists && post.video.localPath) {
try {
const url = await storage.loadMediaFromLocal(post.video.localPath)
setVideoThumbnail(url)
} catch (e) {
// Video doesn't exist locally
}
}
} }
loadMedia() loadMedia()
}, [post.images]) }, [post.images, post.video])
// Convert loaded images to ViewImage format for the embed components
const viewImages = useMemo<AppBskyEmbedImages.ViewImage[]>(() => {
return loadedImages.map(({url, width, height, meta}) => ({
thumb: url,
fullsize: url,
alt: meta.altText || '',
aspectRatio: width && height ? {width, height} : {width: 1, height: 1},
}))
}, [loadedImages])
// Count missing images (images we tried to load but couldn't)
const missingImageCount = (post.images?.length ?? 0) - loadedImages.length
// Nothing to show // Nothing to show
if (viewImages.length === 0 && !post.gif && !post.video) { if (loadedImages.length === 0 && !post.gif && !post.video) {
return null return null
} }
return ( return (
<View style={[a.pt_sm, a.pointer_events_none]}> <MediaPreview.Outer style={[a.pt_xs]}>
{/* Images - use real embed components */} {loadedImages.map((image, i) => (
{viewImages.length === 1 && ( <MediaPreview.ImageItem key={i} thumbnail={image.url} alt={image.alt} />
<AutoSizedImage image={viewImages[0]} hideBadge /> ))}
)}
{viewImages.length > 1 && <ImageLayoutGrid images={viewImages} />}
{/* Missing images note */}
{missingImageCount > 0 && (
<Text style={[a.text_xs, t.atoms.text_contrast_medium, a.mt_xs]}>
<Trans>
{missingImageCount} image{missingImageCount > 1 ? 's' : ''} not
available
</Trans>
</Text>
)}
{/* GIF preview */}
{post.gif && ( {post.gif && (
<View <MediaPreview.GifItem thumbnail={post.gif.url} alt={post.gif.alt} />
style={[
a.rounded_md,
a.overflow_hidden,
t.atoms.bg_contrast_25,
{
aspectRatio:
post.gif.width && post.gif.height
? post.gif.width / post.gif.height
: 16 / 9,
},
]}>
<Image
source={{uri: post.gif.url}}
style={[a.flex_1]}
contentFit="cover"
accessibilityIgnoresInvertColors
/>
</View>
)} )}
{/* Video indicator */}
{post.video && ( {post.video && (
<View <MediaPreview.VideoItem
style={[ thumbnail={videoThumbnail}
a.rounded_md, alt={post.video.altText}
a.p_md, />
a.align_center,
a.justify_center,
t.atoms.bg_contrast_50,
{aspectRatio: 16 / 9},
]}>
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
{post.video.exists ? (
<Trans>Video attached</Trans>
) : (
<Trans>Video not available</Trans>
)} )}
</Text> </MediaPreview.Outer>
</View>
)}
</View>
) )
} }
@@ -26,6 +26,8 @@ export type GifDisplay = {
width: number width: number
/** Height */ /** Height */
height: number height: number
/** Alt text */
alt: string
} }
/** /**