diff --git a/src/components/Post/Embed/ImageEmbed.tsx b/src/components/Post/Embed/ImageEmbed.tsx index 9f1510639e..ccd1ceb270 100644 --- a/src/components/Post/Embed/ImageEmbed.tsx +++ b/src/components/Post/Embed/ImageEmbed.tsx @@ -1,3 +1,4 @@ +import {useRef} from 'react' import {InteractionManager, View} from 'react-native' import {type AnimatedRef} from 'react-native-reanimated' import {Image} from 'expo-image' @@ -25,6 +26,11 @@ export function ImageEmbed({ const {images} = embed.view const galleryEnabled = ax.features.enabled(ax.features.PostGalleryEmbedEnable) + // Captured from AutoSizedImage so the peek-commit handler can reuse the same + // ref + dims that a tap would — keeps the lightbox's return animation intact. + const singleContainerRef = useRef | null>(null) + const singleDimsRef = useRef(null) + if (images.length > 0) { const items = images.map(img => ({ uri: img.fullsize, @@ -57,18 +63,6 @@ export function ImageEmbed({ ) }) } - const onPreviewPress = (index: number) => - openLightbox({ - images: items.map(item => ({ - ...item, - thumbRect: null, - thumbRef: null, - thumbDimensions: null, - thumbBorderRadius: tokens.borderRadius.md, - type: 'image', - })), - index, - }) if (images.length === 1) { const image = images[0] @@ -76,6 +70,11 @@ export function ImageEmbed({ image.aspectRatio && image.aspectRatio.height > 0 ? image.aspectRatio.width / image.aspectRatio.height : undefined + const openFromSingle = () => { + if (singleContainerRef.current) { + onPress(0, [singleContainerRef.current], [singleDimsRef.current]) + } + } return ( onPreviewPress(0)}> + onPreviewPress={openFromSingle}> { + singleContainerRef.current = ref + }} + onDimsChange={dims => { + singleDimsRef.current = dims + }} onPress={(containerRef, dims) => onPress(0, [containerRef], [dims]) } @@ -115,7 +120,6 @@ export function ImageEmbed({ images={images} onPress={onPress} onPressIn={onPressIn} - onPreviewPress={onPreviewPress} viewContext={rest.viewContext} /> @@ -128,7 +132,6 @@ export function ImageEmbed({ images={images} onPress={onPress} onPressIn={onPressIn} - onPreviewPress={onPreviewPress} viewContext={rest.viewContext} /> diff --git a/src/components/images/AutoSizedImage.tsx b/src/components/images/AutoSizedImage.tsx index 2df8d056d6..79b41311cb 100644 --- a/src/components/images/AutoSizedImage.tsx +++ b/src/components/images/AutoSizedImage.tsx @@ -1,4 +1,4 @@ -import {useMemo, useRef} from 'react' +import {useEffect, useMemo, useRef} from 'react' import {type DimensionValue, Pressable, View} from 'react-native' import Animated, { type AnimatedRef, @@ -69,6 +69,8 @@ export function AutoSizedImage({ onPress, onLongPress, onPressIn, + onContainerRef, + onDimsChange, }: { image: AppBskyEmbedImages.ViewImage crop?: 'none' | 'square' | 'constrained' @@ -79,6 +81,11 @@ export function AutoSizedImage({ ) => void onLongPress?: () => void onPressIn?: () => void + /** Fires once with the internal container ref so a parent can drive its + * own lightbox-return animation without waiting for an `onPress`. */ + onContainerRef?: (ref: AnimatedRef) => void + /** Fires when the underlying image reports its natural dimensions. */ + onDimsChange?: (dims: Dimensions) => void }) { const t = useTheme() const {_} = useLingui() @@ -86,6 +93,10 @@ export function AutoSizedImage({ const containerRef = useAnimatedRef() const fetchedDimsRef = useRef<{width: number; height: number} | null>(null) + useEffect(() => { + onContainerRef?.(containerRef) + }, [containerRef, onContainerRef]) + let aspectRatio: number | undefined const dims = image.aspectRatio if (dims) { @@ -122,10 +133,12 @@ export function AutoSizedImage({ accessibilityHint="" onLoad={e => { if (!isContain) { - fetchedDimsRef.current = { + const dims = { width: e.source.width, height: e.source.height, } + fetchedDimsRef.current = dims + onDimsChange?.(dims) } }} loading="lazy" diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx index a1089d2c27..f4ffcd7159 100644 --- a/src/components/images/Gallery/index.tsx +++ b/src/components/images/Gallery/index.tsx @@ -53,7 +53,6 @@ interface GalleryProps { fetchedDims: (Dimensions | null)[], ) => void onPressIn?: (index: number) => void - onPreviewPress?: (index: number) => void viewContext?: PostEmbedViewContext } @@ -97,7 +96,6 @@ export function Gallery({ images, onPress, onPressIn, - onPreviewPress, viewContext, }: GalleryProps) { const {t: l} = useLingui() @@ -267,6 +265,21 @@ export function Gallery({ data={images} keyExtractor={(item, index) => item.thumb + index} renderItem={({item, index}) => { + const openLightboxAtIndex = onPress + ? () => { + ax.metric('post:gallery:openLightbox', { + fromImage: index + 1, // convert to 1-based index for easier analysis + totalImages: images.length, + }) + const refs: AnimatedRef[] = [] + const dims: (Dimensions | null)[] = [] + for (let i = 0; i < images.length; i++) { + refs.push(containerRefsRef.current.get(i)!) + dims.push(thumbDimsRef.current.get(i) ?? null) + } + onPress(index, refs, dims) + } + : undefined return ( { thumbDimsRef.current.set(i, dims) }} - onPress={ - onPress - ? () => { - ax.metric('post:gallery:openLightbox', { - fromImage: index + 1, // convert to 1-based index for easier analysis - totalImages: images.length, - }) - const refs: AnimatedRef[] = [] - const dims: (Dimensions | null)[] = [] - for (let i = 0; i < images.length; i++) { - refs.push(containerRefsRef.current.get(i)!) - dims.push(thumbDimsRef.current.get(i) ?? null) - } - onPress(index, refs, dims) - } - : undefined - } + onPress={openLightboxAtIndex} onPressIn={onPressIn ? () => onPressIn(index) : undefined} - onPreviewPress={ - onPreviewPress ? () => onPreviewPress(index) : undefined - } + onPreviewPress={openLightboxAtIndex} /> ) }} diff --git a/src/components/images/ImageLayoutGrid.tsx b/src/components/images/ImageLayoutGrid.tsx index ef9f351b10..0017ddf9cf 100644 --- a/src/components/images/ImageLayoutGrid.tsx +++ b/src/components/images/ImageLayoutGrid.tsx @@ -17,7 +17,6 @@ interface ImageLayoutGridProps { ) => void onLongPress?: (index: number) => void onPressIn?: (index: number) => void - onPreviewPress?: (index: number) => void style?: StyleProp viewContext?: PostEmbedViewContext } @@ -49,7 +48,6 @@ interface ImageLayoutGridInnerProps { ) => void onLongPress?: (index: number) => void onPressIn?: (index: number) => void - onPreviewPress?: (index: number) => void viewContext?: PostEmbedViewContext gap: {gap: number} } diff --git a/src/components/images/ImageLayoutGridItem.tsx b/src/components/images/ImageLayoutGridItem.tsx index a44d275a17..aac2f5d66f 100644 --- a/src/components/images/ImageLayoutGridItem.tsx +++ b/src/components/images/ImageLayoutGridItem.tsx @@ -27,8 +27,6 @@ interface Props { ) => void onLongPress?: EventFunction onPressIn?: EventFunction - /** Fired from the native iOS peek preview tap. */ - onPreviewPress?: EventFunction imageStyle?: StyleProp viewContext?: PostEmbedViewContext insetBorderStyle?: StyleProp @@ -43,7 +41,6 @@ export function GalleryItem({ onPress, onPressIn, onLongPress, - onPreviewPress, viewContext, insetBorderStyle, containerRefs, @@ -62,22 +59,23 @@ export function GalleryItem({ ? image.aspectRatio.width / image.aspectRatio.height : undefined + // The tap handler and the peek-commit handler do the same thing: open the + // lightbox with this cell's ref + dims so the lightbox's return animation + // can target the original thumbnail. + const openLightboxAtIndex = onPress + ? () => onPress(index, containerRefs, thumbDimsRef.current.slice()) + : undefined + return ( onPreviewPress(index) : undefined - } + onPreviewPress={openLightboxAtIndex} style={a.flex_1}> onPress(index, containerRefs, thumbDimsRef.current.slice()) - : undefined - } + onPress={openLightboxAtIndex} onPressIn={onPressIn ? () => onPressIn(index) : undefined} onLongPress={onLongPress ? () => onLongPress(index) : undefined} android_ripple={{