Lazily measure lightbox thumbnails (#10270)
This commit is contained in:
@@ -1,11 +1,5 @@
|
|||||||
import {InteractionManager, View} from 'react-native'
|
import {InteractionManager, View} from 'react-native'
|
||||||
import {
|
import {type AnimatedRef} from 'react-native-reanimated'
|
||||||
type AnimatedRef,
|
|
||||||
measure,
|
|
||||||
type MeasuredDimensions,
|
|
||||||
runOnJS,
|
|
||||||
runOnUI,
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
|
|
||||||
import {useLightboxControls} from '#/state/lightbox'
|
import {useLightboxControls} from '#/state/lightbox'
|
||||||
@@ -37,34 +31,21 @@ export function ImageEmbed({
|
|||||||
alt: img.alt,
|
alt: img.alt,
|
||||||
dimensions: img.aspectRatio ?? null,
|
dimensions: img.aspectRatio ?? null,
|
||||||
}))
|
}))
|
||||||
const _openLightbox = (
|
|
||||||
index: number,
|
|
||||||
thumbRects: (MeasuredDimensions | null)[],
|
|
||||||
fetchedDims: (Dimensions | null)[],
|
|
||||||
) => {
|
|
||||||
openLightbox({
|
|
||||||
images: items.map((item, i) => ({
|
|
||||||
...item,
|
|
||||||
thumbRect: thumbRects[i] ?? null,
|
|
||||||
thumbDimensions: fetchedDims[i] ?? null,
|
|
||||||
type: 'image',
|
|
||||||
})),
|
|
||||||
index,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const onPress = (
|
const onPress = (
|
||||||
index: number,
|
index: number,
|
||||||
refs: AnimatedRef<any>[],
|
refs: AnimatedRef<any>[],
|
||||||
fetchedDims: (Dimensions | null)[],
|
fetchedDims: (Dimensions | null)[],
|
||||||
) => {
|
) => {
|
||||||
runOnUI(() => {
|
openLightbox({
|
||||||
'worklet'
|
images: items.map((item, i) => ({
|
||||||
const rects: (MeasuredDimensions | null)[] = []
|
...item,
|
||||||
for (const r of refs) {
|
thumbRect: null,
|
||||||
rects.push(measure(r))
|
thumbRef: refs[i] ?? null,
|
||||||
}
|
thumbDimensions: fetchedDims[i] ?? null,
|
||||||
runOnJS(_openLightbox)(index, rects, fetchedDims)
|
type: 'image',
|
||||||
})()
|
})),
|
||||||
|
index,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const onPressIn = (_: number) => {
|
const onPressIn = (_: number) => {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import {memo, useCallback, useEffect, useMemo} from 'react'
|
import {memo, useCallback, useEffect, useMemo} from 'react'
|
||||||
import {Pressable, View} from 'react-native'
|
import {Pressable, View} from 'react-native'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
measure,
|
type AnimatedRef,
|
||||||
type MeasuredDimensions,
|
|
||||||
runOnJS,
|
|
||||||
runOnUI,
|
|
||||||
useAnimatedRef,
|
useAnimatedRef,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
@@ -76,7 +73,7 @@ let ProfileHeaderShell = ({
|
|||||||
const _openLightbox = useCallback(
|
const _openLightbox = useCallback(
|
||||||
(
|
(
|
||||||
uri: string,
|
uri: string,
|
||||||
thumbRect: MeasuredDimensions | null,
|
thumbRef: AnimatedRef<any>,
|
||||||
type: 'circle-avi' | 'rect-avi' | 'image' = 'circle-avi',
|
type: 'circle-avi' | 'rect-avi' | 'image' = 'circle-avi',
|
||||||
) => {
|
) => {
|
||||||
openLightbox({
|
openLightbox({
|
||||||
@@ -84,7 +81,8 @@ let ProfileHeaderShell = ({
|
|||||||
{
|
{
|
||||||
uri,
|
uri,
|
||||||
thumbUri: uri,
|
thumbUri: uri,
|
||||||
thumbRect,
|
thumbRect: null,
|
||||||
|
thumbRef,
|
||||||
dimensions:
|
dimensions:
|
||||||
type === 'circle-avi' || type === 'rect-avi'
|
type === 'circle-avi' || type === 'rect-avi'
|
||||||
? {
|
? {
|
||||||
@@ -130,11 +128,7 @@ let ProfileHeaderShell = ({
|
|||||||
const avatar = profile.avatar
|
const avatar = profile.avatar
|
||||||
const type = profile.associated?.labeler ? 'rect-avi' : 'circle-avi'
|
const type = profile.associated?.labeler ? 'rect-avi' : 'circle-avi'
|
||||||
if (avatar && !(modui.blur && modui.noOverride)) {
|
if (avatar && !(modui.blur && modui.noOverride)) {
|
||||||
runOnUI(() => {
|
_openLightbox(avatar, aviRef, type)
|
||||||
'worklet'
|
|
||||||
const rect = measure(aviRef)
|
|
||||||
runOnJS(_openLightbox)(avatar, rect, type)
|
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -152,11 +146,7 @@ let ProfileHeaderShell = ({
|
|||||||
const modui = moderation.ui('banner')
|
const modui = moderation.ui('banner')
|
||||||
const banner = profile.banner
|
const banner = profile.banner
|
||||||
if (banner && !(modui.blur && modui.noOverride)) {
|
if (banner && !(modui.blur && modui.noOverride)) {
|
||||||
runOnUI(() => {
|
_openLightbox(banner, bannerRef, 'image')
|
||||||
'worklet'
|
|
||||||
const rect = measure(bannerRef)
|
|
||||||
runOnJS(_openLightbox)(banner, rect, 'image')
|
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
}, [profile.banner, moderation, _openLightbox, bannerRef])
|
}, [profile.banner, moderation, _openLightbox, bannerRef])
|
||||||
|
|
||||||
|
|||||||
+39
-8
@@ -1,4 +1,10 @@
|
|||||||
import {createContext, useContext, useEffect, useMemo, useState} from 'react'
|
import {createContext, useContext, useEffect, useMemo, useState} from 'react'
|
||||||
|
import {
|
||||||
|
measure,
|
||||||
|
type MeasuredDimensions,
|
||||||
|
runOnJS,
|
||||||
|
runOnUI,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
import {nanoid} from 'nanoid/non-secure'
|
import {nanoid} from 'nanoid/non-secure'
|
||||||
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
@@ -39,17 +45,42 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
}
|
}
|
||||||
}, [activeLightbox, disableScope, enableScope])
|
}, [activeLightbox, disableScope, enableScope])
|
||||||
|
|
||||||
|
const doOpen = useNonReactiveCallback((lightbox: Omit<Lightbox, 'id'>) => {
|
||||||
|
setActiveLightbox(prevLightbox => {
|
||||||
|
if (prevLightbox) {
|
||||||
|
// Ignore duplicate open requests. If it's already open,
|
||||||
|
// the user has to explicitly close the previous one first.
|
||||||
|
return prevLightbox
|
||||||
|
} else {
|
||||||
|
return {...lightbox, id: nanoid()}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const openLightbox = useNonReactiveCallback(
|
const openLightbox = useNonReactiveCallback(
|
||||||
(lightbox: Omit<Lightbox, 'id'>) => {
|
(lightbox: Omit<Lightbox, 'id'>) => {
|
||||||
setActiveLightbox(prevLightbox => {
|
const thumbRef = lightbox.images[lightbox.index]?.thumbRef
|
||||||
if (prevLightbox) {
|
if (thumbRef) {
|
||||||
// Ignore duplicate open requests. If it's already open,
|
// Measure the tapped image on the UI thread, then open with
|
||||||
// the user has to explicitly close the previous one first.
|
// the rect baked in so it's available from the first render.
|
||||||
return prevLightbox
|
// Only the rect (plain data) goes through runOnJS — AnimatedRef
|
||||||
} else {
|
// objects can't survive serialization across threads.
|
||||||
return {...lightbox, id: nanoid()}
|
const openWithRect = (rect: MeasuredDimensions | null) => {
|
||||||
|
doOpen({
|
||||||
|
...lightbox,
|
||||||
|
images: lightbox.images.map((img, i) =>
|
||||||
|
i === lightbox.index ? {...img, thumbRect: rect} : img,
|
||||||
|
),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
runOnUI(() => {
|
||||||
|
'worklet'
|
||||||
|
const rect = measure(thumbRef)
|
||||||
|
runOnJS(openWithRect)(rect)
|
||||||
|
})()
|
||||||
|
} else {
|
||||||
|
doOpen(lightbox)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {type TransformsStyle} from 'react-native'
|
import {type TransformsStyle} from 'react-native'
|
||||||
import {type MeasuredDimensions} from 'react-native-reanimated'
|
import {
|
||||||
|
type AnimatedRef,
|
||||||
|
type MeasuredDimensions,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
|
||||||
export type Dimensions = {
|
export type Dimensions = {
|
||||||
width: number
|
width: number
|
||||||
@@ -25,6 +28,7 @@ export type ImageSource = {
|
|||||||
thumbUri: string
|
thumbUri: string
|
||||||
thumbDimensions: Dimensions | null
|
thumbDimensions: Dimensions | null
|
||||||
thumbRect: MeasuredDimensions | null
|
thumbRect: MeasuredDimensions | null
|
||||||
|
thumbRef?: AnimatedRef<any> | null
|
||||||
alt?: string
|
alt?: string
|
||||||
type: 'image' | 'circle-avi' | 'rect-avi'
|
type: 'image' | 'circle-avi' | 'rect-avi'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ import Animated, {
|
|||||||
cancelAnimation,
|
cancelAnimation,
|
||||||
interpolate,
|
interpolate,
|
||||||
measure,
|
measure,
|
||||||
|
type MeasuredDimensions,
|
||||||
ReduceMotion,
|
ReduceMotion,
|
||||||
runOnJS,
|
runOnJS,
|
||||||
|
runOnUI,
|
||||||
type SharedValue,
|
type SharedValue,
|
||||||
useAnimatedReaction,
|
useAnimatedReaction,
|
||||||
useAnimatedRef,
|
useAnimatedRef,
|
||||||
@@ -73,12 +75,11 @@ const FAST_SPRING: WithSpringConfig = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function canAnimate(lightbox: Lightbox): boolean {
|
function canAnimate(lightbox: Lightbox): boolean {
|
||||||
return (
|
if (PlatformInfo.getIsReducedMotionEnabled()) {
|
||||||
!PlatformInfo.getIsReducedMotionEnabled() &&
|
return false
|
||||||
lightbox.images.every(
|
}
|
||||||
img => img.thumbRect && (img.dimensions || img.thumbDimensions),
|
const img = lightbox.images[lightbox.index]
|
||||||
)
|
return !!img.thumbRect && !!(img.dimensions || img.thumbDimensions)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ImageViewRoot({
|
export default function ImageViewRoot({
|
||||||
@@ -99,6 +100,9 @@ export default function ImageViewRoot({
|
|||||||
'portrait',
|
'portrait',
|
||||||
)
|
)
|
||||||
const openProgress = useSharedValue(0)
|
const openProgress = useSharedValue(0)
|
||||||
|
const thumbRects = useSharedValue<Record<number, MeasuredDimensions | null>>(
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
if (!activeLightbox && nextLightbox) {
|
if (!activeLightbox && nextLightbox) {
|
||||||
setActiveLightbox(nextLightbox)
|
setActiveLightbox(nextLightbox)
|
||||||
@@ -109,6 +113,12 @@ export default function ImageViewRoot({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initial: Record<number, MeasuredDimensions | null> = {}
|
||||||
|
nextLightbox.images.forEach((img, i) => {
|
||||||
|
initial[i] = img.thumbRect ?? null
|
||||||
|
})
|
||||||
|
thumbRects.set(initial)
|
||||||
|
|
||||||
const isAnimated = canAnimate(nextLightbox)
|
const isAnimated = canAnimate(nextLightbox)
|
||||||
|
|
||||||
// https://github.com/software-mansion/react-native-reanimated/issues/6677
|
// https://github.com/software-mansion/react-native-reanimated/issues/6677
|
||||||
@@ -125,13 +135,21 @@ export default function ImageViewRoot({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [nextLightbox, openProgress])
|
}, [nextLightbox, openProgress, thumbRects])
|
||||||
|
|
||||||
|
const onFullyClosed = useCallback(() => {
|
||||||
|
setActiveLightbox(null)
|
||||||
|
runOnUI(() => {
|
||||||
|
'worklet'
|
||||||
|
thumbRects.set({})
|
||||||
|
})()
|
||||||
|
}, [thumbRects])
|
||||||
|
|
||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => openProgress.get() === 0,
|
() => openProgress.get() === 0,
|
||||||
(isGone, wasGone) => {
|
(isGone, wasGone) => {
|
||||||
if (isGone && !wasGone) {
|
if (isGone && !wasGone) {
|
||||||
runOnJS(setActiveLightbox)(null)
|
runOnJS(onFullyClosed)()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -184,6 +202,7 @@ export default function ImageViewRoot({
|
|||||||
onFlyAway={onFlyAway}
|
onFlyAway={onFlyAway}
|
||||||
safeAreaRef={ref}
|
safeAreaRef={ref}
|
||||||
openProgress={openProgress}
|
openProgress={openProgress}
|
||||||
|
thumbRects={thumbRects}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
@@ -200,6 +219,7 @@ function ImageView({
|
|||||||
onFlyAway,
|
onFlyAway,
|
||||||
safeAreaRef,
|
safeAreaRef,
|
||||||
openProgress,
|
openProgress,
|
||||||
|
thumbRects,
|
||||||
}: {
|
}: {
|
||||||
lightbox: Lightbox
|
lightbox: Lightbox
|
||||||
orientation: 'portrait' | 'landscape'
|
orientation: 'portrait' | 'landscape'
|
||||||
@@ -209,6 +229,7 @@ function ImageView({
|
|||||||
onFlyAway: () => void
|
onFlyAway: () => void
|
||||||
safeAreaRef: AnimatedRef<View>
|
safeAreaRef: AnimatedRef<View>
|
||||||
openProgress: SharedValue<number>
|
openProgress: SharedValue<number>
|
||||||
|
thumbRects: SharedValue<Record<number, MeasuredDimensions | null>>
|
||||||
}) {
|
}) {
|
||||||
const {images, index: initialImageIndex} = lightbox
|
const {images, index: initialImageIndex} = lightbox
|
||||||
const isAnimated = useMemo(() => canAnimate(lightbox), [lightbox])
|
const isAnimated = useMemo(() => canAnimate(lightbox), [lightbox])
|
||||||
@@ -216,7 +237,7 @@ function ImageView({
|
|||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const [imageIndex, setImageIndex] = useState(initialImageIndex)
|
const [imageIndex, setImageIndex] = useState(initialImageIndex)
|
||||||
const [showControls, setShowControls] = useState(true)
|
const [showControls, setShowControls] = useState(true)
|
||||||
const [isAltExpanded, setAltExpanded] = useState(false)
|
const [isAltExpanded, setIsAltExpanded] = useState(false)
|
||||||
const dismissSwipeTranslateY = useSharedValue(0)
|
const dismissSwipeTranslateY = useSharedValue(0)
|
||||||
const isFlyingAway = useSharedValue(false)
|
const isFlyingAway = useSharedValue(false)
|
||||||
|
|
||||||
@@ -287,6 +308,24 @@ function ImageView({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleRequestClose = useCallback(() => {
|
||||||
|
const activeRef = images[imageIndex]?.thumbRef
|
||||||
|
if (isAnimated && activeRef) {
|
||||||
|
runOnUI(() => {
|
||||||
|
'worklet'
|
||||||
|
const rect = measure(activeRef)
|
||||||
|
thumbRects.modify(rects => {
|
||||||
|
'worklet'
|
||||||
|
rects[imageIndex] = rect
|
||||||
|
return rects
|
||||||
|
})
|
||||||
|
runOnJS(onRequestClose)()
|
||||||
|
})()
|
||||||
|
} else {
|
||||||
|
onRequestClose()
|
||||||
|
}
|
||||||
|
}, [isAnimated, images, imageIndex, thumbRects, onRequestClose])
|
||||||
|
|
||||||
const onTap = useCallback(() => {
|
const onTap = useCallback(() => {
|
||||||
setShowControls(show => !show)
|
setShowControls(show => !show)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -355,7 +394,7 @@ function ImageView({
|
|||||||
onTap={onTap}
|
onTap={onTap}
|
||||||
onZoom={onZoom}
|
onZoom={onZoom}
|
||||||
imageSrc={imageSrc}
|
imageSrc={imageSrc}
|
||||||
onRequestClose={onRequestClose}
|
onRequestClose={handleRequestClose}
|
||||||
isScrollViewBeingDragged={isDragging}
|
isScrollViewBeingDragged={isDragging}
|
||||||
showControls={showControls}
|
showControls={showControls}
|
||||||
safeAreaRef={safeAreaRef}
|
safeAreaRef={safeAreaRef}
|
||||||
@@ -364,6 +403,8 @@ function ImageView({
|
|||||||
isActive={i === imageIndex}
|
isActive={i === imageIndex}
|
||||||
dismissSwipeTranslateY={dismissSwipeTranslateY}
|
dismissSwipeTranslateY={dismissSwipeTranslateY}
|
||||||
openProgress={openProgress}
|
openProgress={openProgress}
|
||||||
|
thumbRects={thumbRects}
|
||||||
|
imageIndex={i}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
@@ -372,7 +413,7 @@ function ImageView({
|
|||||||
<Animated.View
|
<Animated.View
|
||||||
style={animatedHeaderStyle}
|
style={animatedHeaderStyle}
|
||||||
renderToHardwareTextureAndroid>
|
renderToHardwareTextureAndroid>
|
||||||
<ImageDefaultHeader onRequestClose={onRequestClose} />
|
<ImageDefaultHeader onRequestClose={handleRequestClose} />
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={animatedFooterStyle}
|
style={animatedFooterStyle}
|
||||||
@@ -381,7 +422,7 @@ function ImageView({
|
|||||||
images={images}
|
images={images}
|
||||||
index={imageIndex}
|
index={imageIndex}
|
||||||
isAltExpanded={isAltExpanded}
|
isAltExpanded={isAltExpanded}
|
||||||
toggleAltExpanded={() => setAltExpanded(e => !e)}
|
toggleAltExpanded={() => setIsAltExpanded(e => !e)}
|
||||||
onPressSave={onPressSave}
|
onPressSave={onPressSave}
|
||||||
onPressShare={onPressShare}
|
onPressShare={onPressShare}
|
||||||
/>
|
/>
|
||||||
@@ -404,6 +445,8 @@ function LightboxImage({
|
|||||||
safeAreaRef,
|
safeAreaRef,
|
||||||
openProgress,
|
openProgress,
|
||||||
dismissSwipeTranslateY,
|
dismissSwipeTranslateY,
|
||||||
|
thumbRects,
|
||||||
|
imageIndex,
|
||||||
}: {
|
}: {
|
||||||
imageSrc: ImageSource
|
imageSrc: ImageSource
|
||||||
onRequestClose: () => void
|
onRequestClose: () => void
|
||||||
@@ -417,6 +460,8 @@ function LightboxImage({
|
|||||||
safeAreaRef: AnimatedRef<View>
|
safeAreaRef: AnimatedRef<View>
|
||||||
openProgress: SharedValue<number>
|
openProgress: SharedValue<number>
|
||||||
dismissSwipeTranslateY: SharedValue<number>
|
dismissSwipeTranslateY: SharedValue<number>
|
||||||
|
thumbRects: SharedValue<Record<number, MeasuredDimensions | null>>
|
||||||
|
imageIndex: number
|
||||||
}) {
|
}) {
|
||||||
const [fetchedDims, setFetchedDims] = useState<Dimensions | null>(null)
|
const [fetchedDims, setFetchedDims] = useState<Dimensions | null>(null)
|
||||||
const dims = fetchedDims ?? imageSrc.dimensions ?? imageSrc.thumbDimensions
|
const dims = fetchedDims ?? imageSrc.dimensions ?? imageSrc.thumbDimensions
|
||||||
@@ -449,7 +494,7 @@ function LightboxImage({
|
|||||||
return safeArea
|
return safeArea
|
||||||
}, [safeAreaRef, heightDelayedForJSThreadOnly, widthDelayedForJSThreadOnly])
|
}, [safeAreaRef, heightDelayedForJSThreadOnly, widthDelayedForJSThreadOnly])
|
||||||
|
|
||||||
const {thumbRect} = imageSrc
|
const {thumbRect: thumbRectJS} = imageSrc
|
||||||
const transforms = useDerivedValue(() => {
|
const transforms = useDerivedValue(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
const safeArea = measureSafeArea()
|
const safeArea = measureSafeArea()
|
||||||
@@ -467,13 +512,21 @@ function LightboxImage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActive && thumbRect && imageAspect && openProgressValue < 1) {
|
if (isActive && imageAspect && openProgressValue < 1) {
|
||||||
return interpolateTransform(
|
let thumbRect
|
||||||
openProgressValue,
|
if (_WORKLET) {
|
||||||
thumbRect,
|
thumbRect = thumbRects.get()[imageIndex]
|
||||||
safeArea,
|
} else {
|
||||||
imageAspect,
|
thumbRect = thumbRectJS
|
||||||
)
|
}
|
||||||
|
if (thumbRect) {
|
||||||
|
return interpolateTransform(
|
||||||
|
openProgressValue,
|
||||||
|
thumbRect,
|
||||||
|
safeArea,
|
||||||
|
imageAspect,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import {useCallback} from 'react'
|
import {useCallback} from 'react'
|
||||||
import {Pressable, View} from 'react-native'
|
import {Pressable, View} from 'react-native'
|
||||||
import Animated, {
|
import Animated, {useAnimatedRef} from 'react-native-reanimated'
|
||||||
measure,
|
|
||||||
type MeasuredDimensions,
|
|
||||||
runOnJS,
|
|
||||||
runOnUI,
|
|
||||||
useAnimatedRef,
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
import {type AppBskyGraphDefs} from '@atproto/api'
|
import {type AppBskyGraphDefs} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -60,14 +54,17 @@ export function ProfileSubpageHeader({
|
|||||||
const canGoBack = navigation.canGoBack()
|
const canGoBack = navigation.canGoBack()
|
||||||
const aviRef = useAnimatedRef()
|
const aviRef = useAnimatedRef()
|
||||||
|
|
||||||
const _openLightbox = useCallback(
|
const onPressAvi = useCallback(() => {
|
||||||
(uri: string, thumbRect: MeasuredDimensions | null) => {
|
if (
|
||||||
|
avatar // TODO && !(view.moderation.avatar.blur && view.moderation.avatar.noOverride)
|
||||||
|
) {
|
||||||
openLightbox({
|
openLightbox({
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
uri,
|
uri: avatar,
|
||||||
thumbUri: uri,
|
thumbUri: avatar,
|
||||||
thumbRect,
|
thumbRect: null,
|
||||||
|
thumbRef: aviRef,
|
||||||
dimensions: {
|
dimensions: {
|
||||||
// It's fine if it's actually smaller but we know it's 1:1.
|
// It's fine if it's actually smaller but we know it's 1:1.
|
||||||
height: 1000,
|
height: 1000,
|
||||||
@@ -79,21 +76,8 @@ export function ProfileSubpageHeader({
|
|||||||
],
|
],
|
||||||
index: 0,
|
index: 0,
|
||||||
})
|
})
|
||||||
},
|
|
||||||
[openLightbox],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onPressAvi = useCallback(() => {
|
|
||||||
if (
|
|
||||||
avatar // TODO && !(view.moderation.avatar.blur && view.moderation.avatar.noOverride)
|
|
||||||
) {
|
|
||||||
runOnUI(() => {
|
|
||||||
'worklet'
|
|
||||||
const rect = measure(aviRef)
|
|
||||||
runOnJS(_openLightbox)(avatar, rect)
|
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
}, [_openLightbox, avatar, aviRef])
|
}, [openLightbox, avatar, aviRef])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user