[Lightbox] Always rely on Expo Image cache (#6189)

* Inline useImageAspectRatio

* Switch AutoSizedImage to read dimensions from Expo Image cache

* Include thumbnail dimensions in image data

* Use dims from Expo Image cache in lightbox

* Fix wiring so all thumbnails get dimensions

* Fix type

* Oops
This commit is contained in:
dan
2024-11-09 22:41:14 +00:00
committed by GitHub
parent 2d73c5a24c
commit 42abd97f61
12 changed files with 105 additions and 146 deletions
-93
View File
@@ -1,93 +0,0 @@
import {useEffect, useState} from 'react'
import {Image} from 'react-native'
import type {Dimensions} from '#/lib/media/types'
type CacheStorageItem<T> = {key: string; value: T}
const createCache = <T>(cacheSize: number) => ({
_storage: [] as CacheStorageItem<T>[],
get(key: string) {
const {value} =
this._storage.find(({key: storageKey}) => storageKey === key) || {}
return value
},
set(key: string, value: T) {
if (this._storage.length >= cacheSize) {
this._storage.shift()
}
this._storage.push({key, value})
},
})
const sizes = createCache<Dimensions>(50)
const activeRequests: Map<string, Promise<Dimensions>> = new Map()
export function get(uri: string): Dimensions | undefined {
return sizes.get(uri)
}
export function fetch(uri: string): Promise<Dimensions> {
const dims = sizes.get(uri)
if (dims) {
return Promise.resolve(dims)
}
const activeRequest = activeRequests.get(uri)
if (activeRequest) {
return activeRequest
}
const prom = new Promise<Dimensions>((resolve, reject) => {
Image.getSize(
uri,
(width: number, height: number) => {
const size = {width, height}
sizes.set(uri, size)
resolve(size)
},
(err: any) => {
console.error('Failed to fetch image dimensions for', uri, err)
reject(new Error('Could not fetch dimensions'))
},
)
}).finally(() => {
activeRequests.delete(uri)
})
activeRequests.set(uri, prom)
return prom
}
export function useImageDimensions({
src,
knownDimensions,
}: {
src: string
knownDimensions: Dimensions | null
}): [number | undefined, Dimensions | undefined] {
const [dims, setDims] = useState(() => knownDimensions ?? get(src))
const [prevSrc, setPrevSrc] = useState(src)
if (src !== prevSrc) {
setDims(knownDimensions ?? get(src))
setPrevSrc(src)
}
useEffect(() => {
let aborted = false
if (dims !== undefined) return
fetch(src).then(newDims => {
if (aborted) return
setDims(newDims)
})
return () => {
aborted = true
}
}, [dims, setDims, src])
let aspectRatio: number | undefined
if (dims) {
aspectRatio = dims.width / dims.height
if (Number.isNaN(aspectRatio)) {
aspectRatio = undefined
}
}
return [aspectRatio, dims]
}
+1
View File
@@ -72,6 +72,7 @@ let ProfileHeaderShell = ({
height: 1000, height: 1000,
width: 1000, width: 1000,
}, },
thumbDimensions: null,
type: 'circle-avi', type: 'circle-avi',
}, },
], ],
@@ -21,10 +21,11 @@ export type Position = {
export type ImageSource = { export type ImageSource = {
uri: string uri: string
dimensions: Dimensions | null
thumbUri: string thumbUri: string
thumbDimensions: Dimensions | null
thumbRect: MeasuredDimensions | null thumbRect: MeasuredDimensions | null
alt?: string alt?: string
dimensions: Dimensions | null
type: 'image' | 'circle-avi' | 'rect-avi' type: 'image' | 'circle-avi' | 'rect-avi'
} }
@@ -41,6 +41,7 @@ type Props = {
onRequestClose: () => void onRequestClose: () => void
onTap: () => void onTap: () => void
onZoom: (isZoomed: boolean) => void onZoom: (isZoomed: boolean) => void
onLoad: (dims: ImageDimensions) => void
isScrollViewBeingDragged: boolean isScrollViewBeingDragged: boolean
showControls: boolean showControls: boolean
measureSafeArea: () => { measureSafeArea: () => {
@@ -66,6 +67,7 @@ const ImageItem = ({
imageSrc, imageSrc,
onTap, onTap,
onZoom, onZoom,
onLoad,
isScrollViewBeingDragged, isScrollViewBeingDragged,
measureSafeArea, measureSafeArea,
imageAspect, imageAspect,
@@ -330,8 +332,8 @@ const ImageItem = ({
transform: scaleAndMoveTransform.concat(manipulationTransform), transform: scaleAndMoveTransform.concat(manipulationTransform),
width: screenSize.width, width: screenSize.width,
maxHeight: screenSize.height, maxHeight: screenSize.height,
aspectRatio: imageAspect,
alignSelf: 'center', alignSelf: 'center',
aspectRatio: imageAspect ?? 1 /* force onLoad */,
} }
}) })
@@ -349,6 +351,7 @@ const ImageItem = ({
return { return {
flex: 1, flex: 1,
transform: cropContentTransform, transform: cropContentTransform,
opacity: imageAspect === undefined ? 0 : 1,
} }
}) })
@@ -393,7 +396,10 @@ const ImageItem = ({
placeholderContentFit="cover" placeholderContentFit="cover"
placeholder={{uri: imageSrc.thumbUri}} placeholder={{uri: imageSrc.thumbUri}}
accessibilityLabel={imageSrc.alt} accessibilityLabel={imageSrc.alt}
onLoad={() => setHasLoaded(false)} onLoad={e => {
setHasLoaded(true)
onLoad({width: e.source.width, height: e.source.height})
}}
style={{flex: 1, borderRadius}} style={{flex: 1, borderRadius}}
accessibilityHint="" accessibilityHint=""
accessibilityIgnoresInvertColors accessibilityIgnoresInvertColors
@@ -38,6 +38,7 @@ type Props = {
onRequestClose: () => void onRequestClose: () => void
onTap: () => void onTap: () => void
onZoom: (scaled: boolean) => void onZoom: (scaled: boolean) => void
onLoad: (dims: ImageDimensions) => void
isScrollViewBeingDragged: boolean isScrollViewBeingDragged: boolean
showControls: boolean showControls: boolean
measureSafeArea: () => { measureSafeArea: () => {
@@ -64,6 +65,7 @@ const ImageItem = ({
imageSrc, imageSrc,
onTap, onTap,
onZoom, onZoom,
onLoad,
showControls, showControls,
measureSafeArea, measureSafeArea,
imageAspect, imageAspect,
@@ -162,8 +164,9 @@ const ImageItem = ({
transform: cropFrameTransform, transform: cropFrameTransform,
width: screenSize.width, width: screenSize.width,
maxHeight: screenSize.height, maxHeight: screenSize.height,
aspectRatio: imageAspect,
alignSelf: 'center', alignSelf: 'center',
aspectRatio: imageAspect ?? 1 /* force onLoad */,
opacity: imageAspect === undefined ? 0 : 1,
} }
}) })
@@ -172,7 +175,8 @@ const ImageItem = ({
return { return {
transform: cropContentTransform, transform: cropContentTransform,
width: '100%', width: '100%',
aspectRatio: imageAspect, aspectRatio: imageAspect ?? 1 /* force onLoad */,
opacity: imageAspect === undefined ? 0 : 1,
} }
}) })
@@ -224,7 +228,10 @@ const ImageItem = ({
accessibilityHint="" accessibilityHint=""
enableLiveTextInteraction={showControls && !scaled} enableLiveTextInteraction={showControls && !scaled}
accessibilityIgnoresInvertColors accessibilityIgnoresInvertColors
onLoad={() => setHasLoaded(true)} onLoad={e => {
setHasLoaded(true)
onLoad({width: e.source.width, height: e.source.height})
}}
/> />
</Animated.View> </Animated.View>
</Animated.View> </Animated.View>
@@ -5,6 +5,7 @@ import {View} from 'react-native'
import {PanGesture} from 'react-native-gesture-handler' import {PanGesture} from 'react-native-gesture-handler'
import {SharedValue} from 'react-native-reanimated' import {SharedValue} from 'react-native-reanimated'
import {Dimensions} from '#/lib/media/types'
import { import {
Dimensions as ImageDimensions, Dimensions as ImageDimensions,
ImageSource, ImageSource,
@@ -16,6 +17,7 @@ type Props = {
onRequestClose: () => void onRequestClose: () => void
onTap: () => void onTap: () => void
onZoom: (scaled: boolean) => void onZoom: (scaled: boolean) => void
onLoad: (dims: Dimensions) => void
isScrollViewBeingDragged: boolean isScrollViewBeingDragged: boolean
showControls: boolean showControls: boolean
measureSafeArea: () => { measureSafeArea: () => {
+15 -7
View File
@@ -42,7 +42,7 @@ import {
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {useImageDimensions} from '#/lib/media/image-sizes' import {Dimensions} from '#/lib/media/types'
import {colors, s} from '#/lib/styles' import {colors, s} from '#/lib/styles'
import {isIOS} from '#/platform/detection' import {isIOS} from '#/platform/detection'
import {Lightbox} from '#/state/lightbox' import {Lightbox} from '#/state/lightbox'
@@ -92,7 +92,9 @@ export default function ImageViewRoot({
const canAnimate = const canAnimate =
!PlatformInfo.getIsReducedMotionEnabled() && !PlatformInfo.getIsReducedMotionEnabled() &&
nextLightbox.images.every(img => img.dimensions && img.thumbRect) nextLightbox.images.every(
img => img.thumbRect && (img.dimensions || img.thumbDimensions),
)
// https://github.com/software-mansion/react-native-reanimated/issues/6677 // https://github.com/software-mansion/react-native-reanimated/issues/6677
requestAnimationFrame(() => { requestAnimationFrame(() => {
@@ -345,10 +347,15 @@ function LightboxImage({
openProgress: SharedValue<number> openProgress: SharedValue<number>
dismissSwipeTranslateY: SharedValue<number> dismissSwipeTranslateY: SharedValue<number>
}) { }) {
const [imageAspect, imageDimensions] = useImageDimensions({ const [fetchedDims, setFetchedDims] = React.useState<Dimensions | null>(null)
src: imageSrc.uri, const dims = fetchedDims ?? imageSrc.dimensions ?? imageSrc.thumbDimensions
knownDimensions: imageSrc.dimensions, let imageAspect: number | undefined
}) if (dims) {
imageAspect = dims.width / dims.height
if (Number.isNaN(imageAspect)) {
imageAspect = undefined
}
}
const safeFrameDelayedForJSThreadOnly = useSafeAreaFrame() const safeFrameDelayedForJSThreadOnly = useSafeAreaFrame()
const safeInsetsDelayedForJSThreadOnly = useSafeAreaInsets() const safeInsetsDelayedForJSThreadOnly = useSafeAreaInsets()
@@ -452,11 +459,12 @@ function LightboxImage({
onTap={onTap} onTap={onTap}
onZoom={onZoom} onZoom={onZoom}
onRequestClose={onRequestClose} onRequestClose={onRequestClose}
onLoad={setFetchedDims}
isScrollViewBeingDragged={isScrollViewBeingDragged} isScrollViewBeingDragged={isScrollViewBeingDragged}
showControls={showControls} showControls={showControls}
measureSafeArea={measureSafeArea} measureSafeArea={measureSafeArea}
imageAspect={imageAspect} imageAspect={imageAspect}
imageDimensions={imageDimensions} imageDimensions={dims ?? undefined}
dismissSwipePan={dismissSwipePan} dismissSwipePan={dismissSwipePan}
transforms={transforms} transforms={transforms}
/> />
@@ -87,6 +87,7 @@ export function ProfileSubpageHeader({
height: 1000, height: 1000,
width: 1000, width: 1000,
}, },
thumbDimensions: null,
type: 'rect-avi', type: 'rect-avi',
}, },
], ],
+30 -37
View File
@@ -6,8 +6,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useImageDimensions} from '#/lib/media/image-sizes' import type {Dimensions} from '#/lib/media/types'
import {Dimensions} from '#/lib/media/types'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
@@ -15,30 +14,6 @@ import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/compone
import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
function useImageAspectRatio({
src,
knownDimensions,
}: {
src: string
knownDimensions: Dimensions | null
}) {
const [raw] = useImageDimensions({src, knownDimensions})
let constrained: number | undefined
let max: number | undefined
let isCropped: boolean | undefined
if (raw !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(raw, ratio)
max = Math.max(raw, 0.25) // max of 1:4 in thread
isCropped = raw < constrained
}
return {
constrained,
max,
isCropped,
}
}
export function ConstrainedImage({ export function ConstrainedImage({
aspectRatio, aspectRatio,
fullBleed, fullBleed,
@@ -93,23 +68,38 @@ export function AutoSizedImage({
image: AppBskyEmbedImages.ViewImage image: AppBskyEmbedImages.ViewImage
crop?: 'none' | 'square' | 'constrained' crop?: 'none' | 'square' | 'constrained'
hideBadge?: boolean hideBadge?: boolean
onPress?: (containerRef: AnimatedRef<React.Component<{}, {}, any>>) => void onPress?: (
containerRef: AnimatedRef<React.Component<{}, {}, any>>,
fetchedDims: Dimensions | null,
) => void
onLongPress?: () => void onLongPress?: () => void
onPressIn?: () => void onPressIn?: () => void
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const largeAlt = useLargeAltBadgeEnabled() const largeAlt = useLargeAltBadgeEnabled()
const {
constrained,
max,
isCropped: rawIsCropped,
} = useImageAspectRatio({
src: image.thumb,
knownDimensions: image.aspectRatio ?? null,
})
const containerRef = useAnimatedRef() const containerRef = useAnimatedRef()
const [fetchedDims, setFetchedDims] = React.useState<Dimensions | null>(null)
const dims = fetchedDims ?? image.aspectRatio
let aspectRatio: number | undefined
if (dims) {
aspectRatio = dims.width / dims.height
if (Number.isNaN(aspectRatio)) {
aspectRatio = undefined
}
}
let constrained: number | undefined
let max: number | undefined
let rawIsCropped: boolean | undefined
if (aspectRatio !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(aspectRatio, ratio)
max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
rawIsCropped = aspectRatio < constrained
}
const cropDisabled = crop === 'none' const cropDisabled = crop === 'none'
const isCropped = rawIsCropped && !cropDisabled const isCropped = rawIsCropped && !cropDisabled
const hasAlt = !!image.alt const hasAlt = !!image.alt
@@ -123,6 +113,9 @@ export function AutoSizedImage({
accessibilityIgnoresInvertColors accessibilityIgnoresInvertColors
accessibilityLabel={image.alt} accessibilityLabel={image.alt}
accessibilityHint="" accessibilityHint=""
onLoad={e => {
setFetchedDims({width: e.source.width, height: e.source.height})
}}
/> />
<MediaInsetBorder /> <MediaInsetBorder />
@@ -194,7 +187,7 @@ export function AutoSizedImage({
if (cropDisabled) { if (cropDisabled) {
return ( return (
<Pressable <Pressable
onPress={() => onPress?.(containerRef)} onPress={() => onPress?.(containerRef, fetchedDims)}
onLongPress={onLongPress} onLongPress={onLongPress}
onPressIn={onPressIn} onPressIn={onPressIn}
// alt here is what screen readers actually use // alt here is what screen readers actually use
@@ -216,7 +209,7 @@ export function AutoSizedImage({
fullBleed={crop === 'square'} fullBleed={crop === 'square'}
aspectRatio={constrained ?? 1}> aspectRatio={constrained ?? 1}>
<Pressable <Pressable
onPress={() => onPress?.(containerRef)} onPress={() => onPress?.(containerRef, fetchedDims)}
onLongPress={onLongPress} onLongPress={onLongPress}
onPressIn={onPressIn} onPressIn={onPressIn}
// alt here is what screen readers actually use // alt here is what screen readers actually use
+15 -1
View File
@@ -6,6 +6,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Dimensions} from '#/lib/media/types'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types' import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
@@ -20,6 +21,7 @@ interface Props {
onPress?: ( onPress?: (
index: number, index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[], containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
fetchedDims: (Dimensions | null)[],
) => void ) => void
onLongPress?: EventFunction onLongPress?: EventFunction
onPressIn?: EventFunction onPressIn?: EventFunction
@@ -27,6 +29,7 @@ interface Props {
viewContext?: PostEmbedViewContext viewContext?: PostEmbedViewContext
insetBorderStyle?: StyleProp<ViewStyle> insetBorderStyle?: StyleProp<ViewStyle>
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[] containerRefs: AnimatedRef<React.Component<{}, {}, any>>[]
thumbDimsRef: React.MutableRefObject<(Dimensions | null)[]>
} }
export function GalleryItem({ export function GalleryItem({
@@ -39,6 +42,7 @@ export function GalleryItem({
viewContext, viewContext,
insetBorderStyle, insetBorderStyle,
containerRefs, containerRefs,
thumbDimsRef,
}: Props) { }: Props) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
@@ -53,7 +57,11 @@ export function GalleryItem({
ref={containerRefs[index]} ref={containerRefs[index]}
collapsable={false}> collapsable={false}>
<Pressable <Pressable
onPress={onPress ? () => onPress(index, containerRefs) : undefined} onPress={
onPress
? () => onPress(index, containerRefs, thumbDimsRef.current.slice())
: undefined
}
onPressIn={onPressIn ? () => onPressIn(index) : undefined} onPressIn={onPressIn ? () => onPressIn(index) : undefined}
onLongPress={onLongPress ? () => onLongPress(index) : undefined} onLongPress={onLongPress ? () => onLongPress(index) : undefined}
style={[ style={[
@@ -72,6 +80,12 @@ export function GalleryItem({
accessibilityLabel={image.alt} accessibilityLabel={image.alt}
accessibilityHint="" accessibilityHint=""
accessibilityIgnoresInvertColors accessibilityIgnoresInvertColors
onLoad={e => {
thumbDimsRef.current[index] = {
width: e.source.width,
height: e.source.height,
}
}}
/> />
<MediaInsetBorder style={insetBorderStyle} /> <MediaInsetBorder style={insetBorderStyle} />
</Pressable> </Pressable>
@@ -5,6 +5,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types' import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
import {atoms as a, useBreakpoints} from '#/alf' import {atoms as a, useBreakpoints} from '#/alf'
import {Dimensions} from '../../lightbox/ImageViewing/@types'
import {GalleryItem} from './Gallery' import {GalleryItem} from './Gallery'
interface ImageLayoutGridProps { interface ImageLayoutGridProps {
@@ -12,6 +13,7 @@ interface ImageLayoutGridProps {
onPress?: ( onPress?: (
index: number, index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[], containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
fetchedDims: (Dimensions | null)[],
) => void ) => void
onLongPress?: (index: number) => void onLongPress?: (index: number) => void
onPressIn?: (index: number) => void onPressIn?: (index: number) => void
@@ -42,6 +44,7 @@ interface ImageLayoutGridInnerProps {
onPress?: ( onPress?: (
index: number, index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[], containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
fetchedDims: (Dimensions | null)[],
) => void ) => void
onLongPress?: (index: number) => void onLongPress?: (index: number) => void
onPressIn?: (index: number) => void onPressIn?: (index: number) => void
@@ -57,6 +60,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
const containerRef2 = useAnimatedRef() const containerRef2 = useAnimatedRef()
const containerRef3 = useAnimatedRef() const containerRef3 = useAnimatedRef()
const containerRef4 = useAnimatedRef() const containerRef4 = useAnimatedRef()
const thumbDimsRef = React.useRef<(Dimensions | null)[]>([])
switch (count) { switch (count) {
case 2: { case 2: {
@@ -69,6 +73,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
index={0} index={0}
insetBorderStyle={noCorners(['topRight', 'bottomRight'])} insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
<View style={[a.flex_1, {aspectRatio: 1}]}> <View style={[a.flex_1, {aspectRatio: 1}]}>
@@ -77,6 +82,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
index={1} index={1}
insetBorderStyle={noCorners(['topLeft', 'bottomLeft'])} insetBorderStyle={noCorners(['topLeft', 'bottomLeft'])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
</View> </View>
@@ -93,6 +99,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
index={0} index={0}
insetBorderStyle={noCorners(['topRight', 'bottomRight'])} insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
<View style={[a.flex_1, {aspectRatio: 1}, gap]}> <View style={[a.flex_1, {aspectRatio: 1}, gap]}>
@@ -106,6 +113,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomRight', 'bottomRight',
])} ])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
<View style={[a.flex_1]}> <View style={[a.flex_1]}>
@@ -118,6 +126,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'topRight', 'topRight',
])} ])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
</View> </View>
@@ -145,6 +154,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomRight', 'bottomRight',
])} ])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
<View style={[a.flex_1, {aspectRatio: 1.5}]}> <View style={[a.flex_1, {aspectRatio: 1.5}]}>
@@ -157,6 +167,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomRight', 'bottomRight',
])} ])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
</View> </View>
@@ -171,6 +182,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomRight', 'bottomRight',
])} ])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
<View style={[a.flex_1, {aspectRatio: 1.5}]}> <View style={[a.flex_1, {aspectRatio: 1.5}]}>
@@ -183,6 +195,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'topRight', 'topRight',
])} ])}
containerRefs={containerRefs} containerRefs={containerRefs}
thumbDimsRef={thumbDimsRef}
/> />
</View> </View>
</View> </View>
+8 -2
View File
@@ -35,6 +35,7 @@ import {atoms as a, useTheme} from '#/alf'
import * as ListCard from '#/components/ListCard' import * as ListCard from '#/components/ListCard'
import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard' import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
import {ContentHider} from '../../../../components/moderation/ContentHider' import {ContentHider} from '../../../../components/moderation/ContentHider'
import {Dimensions} from '../../lightbox/ImageViewing/@types'
import {AutoSizedImage} from '../images/AutoSizedImage' import {AutoSizedImage} from '../images/AutoSizedImage'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid' import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ExternalLinkEmbed} from './ExternalLinkEmbed' import {ExternalLinkEmbed} from './ExternalLinkEmbed'
@@ -148,11 +149,13 @@ export function PostEmbeds({
const _openLightbox = ( const _openLightbox = (
index: number, index: number,
thumbRects: (MeasuredDimensions | null)[], thumbRects: (MeasuredDimensions | null)[],
fetchedDims: (Dimensions | null)[],
) => { ) => {
openLightbox({ openLightbox({
images: items.map((item, i) => ({ images: items.map((item, i) => ({
...item, ...item,
thumbRect: thumbRects[i] ?? null, thumbRect: thumbRects[i] ?? null,
thumbDimensions: fetchedDims[i] ?? null,
type: 'image', type: 'image',
})), })),
index, index,
@@ -161,11 +164,12 @@ export function PostEmbeds({
const onPress = ( const onPress = (
index: number, index: number,
refs: AnimatedRef<React.Component<{}, {}, any>>[], refs: AnimatedRef<React.Component<{}, {}, any>>[],
fetchedDims: (Dimensions | null)[],
) => { ) => {
runOnUI(() => { runOnUI(() => {
'worklet' 'worklet'
const rects = refs.map(ref => (ref ? measure(ref) : null)) const rects = refs.map(ref => (ref ? measure(ref) : null))
runOnJS(_openLightbox)(index, rects) runOnJS(_openLightbox)(index, rects, fetchedDims)
})() })()
} }
const onPressIn = (_: number) => { const onPressIn = (_: number) => {
@@ -189,7 +193,9 @@ export function PostEmbeds({
: 'constrained' : 'constrained'
} }
image={image} image={image}
onPress={containerRef => onPress(0, [containerRef])} onPress={(containerRef, dims) =>
onPress(0, [containerRef], [dims])
}
onPressIn={() => onPressIn(0)} onPressIn={() => onPressIn(0)}
hideBadge={ hideBadge={
viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia