Use minimal media mode for draft display - REVERT IF NEEDED
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user