Measure all rects for embeds

This commit is contained in:
Dan Abramov
2024-11-07 01:59:10 +00:00
parent a6d009d06b
commit a3b037af5c
7 changed files with 49 additions and 22 deletions
+1 -1
View File
@@ -59,6 +59,7 @@ let ProfileHeaderShell = ({
{ {
uri: profile.avatar, uri: profile.avatar,
thumbUri: profile.avatar, thumbUri: profile.avatar,
thumbRect: null,
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,
@@ -68,7 +69,6 @@ let ProfileHeaderShell = ({
}, },
], ],
index: 0, index: 0,
thumbDims: null,
}) })
} }
}, [openLightbox, profile, moderation]) }, [openLightbox, profile, moderation])
-2
View File
@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import type {MeasuredDimensions} 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'
@@ -8,7 +7,6 @@ import {ImageSource} from '#/view/com/lightbox/ImageViewing/@types'
export type Lightbox = { export type Lightbox = {
id: string id: string
images: ImageSource[] images: ImageSource[]
thumbDims: MeasuredDimensions | null
index: number index: number
} }
@@ -6,6 +6,8 @@
* *
*/ */
import {MeasuredDimensions} from 'react-native-reanimated'
export type Dimensions = { export type Dimensions = {
width: number width: number
height: number height: number
@@ -19,6 +21,7 @@ export type Position = {
export type ImageSource = { export type ImageSource = {
uri: string uri: string
thumbUri: string thumbUri: string
thumbRect: MeasuredDimensions | null
alt?: string alt?: string
dimensions: Dimensions | null dimensions: Dimensions | null
type: 'image' | 'circle-avi' | 'rect-avi' type: 'image' | 'circle-avi' | 'rect-avi'
@@ -75,6 +75,7 @@ export function ProfileSubpageHeader({
{ {
uri: avatar, uri: avatar,
thumbUri: avatar, thumbUri: avatar,
thumbRect: null,
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,
@@ -84,7 +85,6 @@ export function ProfileSubpageHeader({
}, },
], ],
index: 0, index: 0,
thumbDims: null,
}) })
} }
}, [openLightbox, avatar]) }, [openLightbox, avatar])
+6 -5
View File
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import {Pressable, StyleProp, View, ViewStyle} from 'react-native' import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
import Animated, {AnimatedRef, useAnimatedRef} from 'react-native-reanimated' import Animated, {AnimatedRef} from 'react-native-reanimated'
import {Image, ImageStyle} from 'expo-image' import {Image, ImageStyle} from 'expo-image'
import {AppBskyEmbedImages} from '@atproto/api' import {AppBskyEmbedImages} from '@atproto/api'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
@@ -19,13 +19,14 @@ interface Props {
index: number index: number
onPress?: ( onPress?: (
index: number, index: number,
containerRef: AnimatedRef<React.Component<{}, {}, any>>, containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
) => void ) => void
onLongPress?: EventFunction onLongPress?: EventFunction
onPressIn?: EventFunction onPressIn?: EventFunction
imageStyle?: StyleProp<ImageStyle> imageStyle?: StyleProp<ImageStyle>
viewContext?: PostEmbedViewContext viewContext?: PostEmbedViewContext
insetBorderStyle?: StyleProp<ViewStyle> insetBorderStyle?: StyleProp<ViewStyle>
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[]
} }
export function GalleryItem({ export function GalleryItem({
@@ -37,6 +38,7 @@ export function GalleryItem({
onLongPress, onLongPress,
viewContext, viewContext,
insetBorderStyle, insetBorderStyle,
containerRefs,
}: Props) { }: Props) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
@@ -45,11 +47,10 @@ export function GalleryItem({
const hasAlt = !!image.alt const hasAlt = !!image.alt
const hideBadges = const hideBadges =
viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
const containerRef = useAnimatedRef()
return ( return (
<Animated.View style={a.flex_1} ref={containerRef}> <Animated.View style={a.flex_1} ref={containerRefs[index]}>
<Pressable <Pressable
onPress={onPress ? () => onPress(index, containerRef) : undefined} onPress={onPress ? () => onPress(index, containerRefs) : undefined}
onPressIn={onPressIn ? () => onPressIn(index) : undefined} onPressIn={onPressIn ? () => onPressIn(index) : undefined}
onLongPress={onLongPress ? () => onLongPress(index) : undefined} onLongPress={onLongPress ? () => onLongPress(index) : undefined}
style={[ style={[
+31 -6
View File
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {AnimatedRef} from 'react-native-reanimated' import {AnimatedRef, useAnimatedRef} from 'react-native-reanimated'
import {AppBskyEmbedImages} from '@atproto/api' import {AppBskyEmbedImages} from '@atproto/api'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types' import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
@@ -11,7 +11,7 @@ interface ImageLayoutGridProps {
images: AppBskyEmbedImages.ViewImage[] images: AppBskyEmbedImages.ViewImage[]
onPress?: ( onPress?: (
index: number, index: number,
containerRef: AnimatedRef<React.Component<{}, {}, any>>, containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
) => void ) => void
onLongPress?: (index: number) => void onLongPress?: (index: number) => void
onPressIn?: (index: number) => void onPressIn?: (index: number) => void
@@ -41,7 +41,7 @@ interface ImageLayoutGridInnerProps {
images: AppBskyEmbedImages.ViewImage[] images: AppBskyEmbedImages.ViewImage[]
onPress?: ( onPress?: (
index: number, index: number,
containerRef: AnimatedRef<React.Component<{}, {}, any>>, containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
) => void ) => void
onLongPress?: (index: number) => void onLongPress?: (index: number) => void
onPressIn?: (index: number) => void onPressIn?: (index: number) => void
@@ -53,8 +53,14 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
const gap = props.gap const gap = props.gap
const count = props.images.length const count = props.images.length
const containerRef1 = useAnimatedRef()
const containerRef2 = useAnimatedRef()
const containerRef3 = useAnimatedRef()
const containerRef4 = useAnimatedRef()
switch (count) { switch (count) {
case 2: case 2: {
const containerRefs = [containerRef1, containerRef2]
return ( return (
<View style={[a.flex_1, a.flex_row, gap]}> <View style={[a.flex_1, a.flex_row, gap]}>
<View style={[a.flex_1, {aspectRatio: 1}]}> <View style={[a.flex_1, {aspectRatio: 1}]}>
@@ -62,6 +68,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
{...props} {...props}
index={0} index={0}
insetBorderStyle={noCorners(['topRight', 'bottomRight'])} insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
containerRefs={containerRefs}
/> />
</View> </View>
<View style={[a.flex_1, {aspectRatio: 1}]}> <View style={[a.flex_1, {aspectRatio: 1}]}>
@@ -69,12 +76,15 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
{...props} {...props}
index={1} index={1}
insetBorderStyle={noCorners(['topLeft', 'bottomLeft'])} insetBorderStyle={noCorners(['topLeft', 'bottomLeft'])}
containerRefs={containerRefs}
/> />
</View> </View>
</View> </View>
) )
}
case 3: case 3: {
const containerRefs = [containerRef1, containerRef2, containerRef3]
return ( return (
<View style={[a.flex_1, a.flex_row, gap]}> <View style={[a.flex_1, a.flex_row, gap]}>
<View style={[a.flex_1, {aspectRatio: 1}]}> <View style={[a.flex_1, {aspectRatio: 1}]}>
@@ -82,6 +92,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
{...props} {...props}
index={0} index={0}
insetBorderStyle={noCorners(['topRight', 'bottomRight'])} insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
containerRefs={containerRefs}
/> />
</View> </View>
<View style={[a.flex_1, {aspectRatio: 1}, gap]}> <View style={[a.flex_1, {aspectRatio: 1}, gap]}>
@@ -94,6 +105,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomLeft', 'bottomLeft',
'bottomRight', 'bottomRight',
])} ])}
containerRefs={containerRefs}
/> />
</View> </View>
<View style={[a.flex_1]}> <View style={[a.flex_1]}>
@@ -105,13 +117,21 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomLeft', 'bottomLeft',
'topRight', 'topRight',
])} ])}
containerRefs={containerRefs}
/> />
</View> </View>
</View> </View>
</View> </View>
) )
}
case 4: case 4: {
const containerRefs = [
containerRef1,
containerRef2,
containerRef3,
containerRef4,
]
return ( return (
<> <>
<View style={[a.flex_row, gap]}> <View style={[a.flex_row, gap]}>
@@ -124,6 +144,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'topRight', 'topRight',
'bottomRight', 'bottomRight',
])} ])}
containerRefs={containerRefs}
/> />
</View> </View>
<View style={[a.flex_1, {aspectRatio: 1.5}]}> <View style={[a.flex_1, {aspectRatio: 1.5}]}>
@@ -135,6 +156,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomLeft', 'bottomLeft',
'bottomRight', 'bottomRight',
])} ])}
containerRefs={containerRefs}
/> />
</View> </View>
</View> </View>
@@ -148,6 +170,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'topRight', 'topRight',
'bottomRight', 'bottomRight',
])} ])}
containerRefs={containerRefs}
/> />
</View> </View>
<View style={[a.flex_1, {aspectRatio: 1.5}]}> <View style={[a.flex_1, {aspectRatio: 1.5}]}>
@@ -159,11 +182,13 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
'bottomLeft', 'bottomLeft',
'topRight', 'topRight',
])} ])}
containerRefs={containerRefs}
/> />
</View> </View>
</View> </View>
</> </>
) )
}
default: default:
return null return null
+7 -7
View File
@@ -149,25 +149,25 @@ export function PostEmbeds({
})) }))
const _openLightbox = ( const _openLightbox = (
index: number, index: number,
thumbDims: MeasuredDimensions | null, thumbRects: (MeasuredDimensions | null)[],
) => { ) => {
openLightbox({ openLightbox({
images: items.map(item => ({ images: items.map((item, i) => ({
...item, ...item,
thumbRect: thumbRects[i] ?? null,
type: 'image', type: 'image',
})), })),
index, index,
thumbDims,
}) })
} }
const onPress = ( const onPress = (
index: number, index: number,
ref: AnimatedRef<React.Component<{}, {}, any>>, refs: AnimatedRef<React.Component<{}, {}, any>>[],
) => { ) => {
runOnUI(() => { runOnUI(() => {
'worklet' 'worklet'
const dims = measure(ref) const rects = refs.map(ref => (ref ? measure(ref) : null))
runOnJS(_openLightbox)(index, dims) runOnJS(_openLightbox)(index, rects)
})() })()
} }
const onPressIn = (_: number) => { const onPressIn = (_: number) => {
@@ -191,7 +191,7 @@ export function PostEmbeds({
: 'constrained' : 'constrained'
} }
image={image} image={image}
onPress={() => onPress(0, containerRef)} onPress={() => onPress(0, [containerRef])}
onPressIn={() => onPressIn(0)} onPressIn={() => onPressIn(0)}
hideBadge={ hideBadge={
viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia