Measure tapped image coordinates before opening lightbox (#6001)

* Measure image on press

* Pass dimensions to the lightbox component
This commit is contained in:
dan
2024-10-31 16:24:15 +00:00
committed by GitHub
parent 6f4703e814
commit 1e32327de0
8 changed files with 56 additions and 11 deletions
+1
View File
@@ -57,6 +57,7 @@ let ProfileHeaderShell = ({
openLightbox({ openLightbox({
type: 'profile-image', type: 'profile-image',
profile: profile, profile: profile,
thumbDims: null,
}) })
} }
}, [openLightbox, profile, moderation]) }, [openLightbox, profile, moderation])
+3
View File
@@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import type {MeasuredDimensions} from 'react-native-reanimated'
import {AppBskyActorDefs} from '@atproto/api' import {AppBskyActorDefs} from '@atproto/api'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
@@ -6,6 +7,7 @@ import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
type ProfileImageLightbox = { type ProfileImageLightbox = {
type: 'profile-image' type: 'profile-image'
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
thumbDims: null
} }
type ImagesLightboxItem = { type ImagesLightboxItem = {
@@ -17,6 +19,7 @@ type ImagesLightboxItem = {
type ImagesLightbox = { type ImagesLightbox = {
type: 'images' type: 'images'
images: ImagesLightboxItem[] images: ImagesLightboxItem[]
thumbDims: MeasuredDimensions | null
index: number index: number
} }
@@ -11,6 +11,7 @@
import React, {ComponentType, useCallback, useMemo, useState} from 'react' import React, {ComponentType, useCallback, useMemo, useState} from 'react'
import {Platform, StyleSheet, View} from 'react-native' import {Platform, StyleSheet, View} from 'react-native'
import PagerView from 'react-native-pager-view' import PagerView from 'react-native-pager-view'
import {MeasuredDimensions} from 'react-native-reanimated'
import Animated, {useAnimatedStyle, withSpring} from 'react-native-reanimated' import Animated, {useAnimatedStyle, withSpring} from 'react-native-reanimated'
import {Edge, SafeAreaView} from 'react-native-safe-area-context' import {Edge, SafeAreaView} from 'react-native-safe-area-context'
@@ -20,6 +21,7 @@ import ImageItem from './components/ImageItem/ImageItem'
type Props = { type Props = {
images: ImageSource[] images: ImageSource[]
thumbDims: MeasuredDimensions | null
initialImageIndex: number initialImageIndex: number
visible: boolean visible: boolean
onRequestClose: () => void onRequestClose: () => void
@@ -32,6 +34,7 @@ const DEFAULT_BG_COLOR = '#000'
function ImageViewing({ function ImageViewing({
images, images,
thumbDims: _thumbDims, // TODO: Pass down and use for animation.
initialImageIndex, initialImageIndex,
visible, visible,
onRequestClose, onRequestClose,
+2
View File
@@ -35,6 +35,7 @@ export function Lightbox() {
{uri: opts.profile.avatar || '', thumbUri: opts.profile.avatar || ''}, {uri: opts.profile.avatar || '', thumbUri: opts.profile.avatar || ''},
]} ]}
initialImageIndex={0} initialImageIndex={0}
thumbDims={opts.thumbDims}
visible visible
onRequestClose={onClose} onRequestClose={onClose}
FooterComponent={LightboxFooter} FooterComponent={LightboxFooter}
@@ -46,6 +47,7 @@ export function Lightbox() {
<ImageView <ImageView
images={opts.images.map(img => ({...img}))} images={opts.images.map(img => ({...img}))}
initialImageIndex={opts.index} initialImageIndex={opts.index}
thumbDims={opts.thumbDims}
visible visible
onRequestClose={onClose} onRequestClose={onClose}
FooterComponent={LightboxFooter} FooterComponent={LightboxFooter}
@@ -74,6 +74,7 @@ export function ProfileSubpageHeader({
type: 'images', type: 'images',
images: [{uri: avatar, thumbUri: avatar}], images: [{uri: avatar, thumbUri: avatar}],
index: 0, index: 0,
thumbDims: null,
}) })
} }
}, [openLightbox, avatar]) }, [openLightbox, avatar])
+9 -4
View File
@@ -1,5 +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 {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'
@@ -16,7 +17,10 @@ type EventFunction = (index: number) => void
interface Props { interface Props {
images: AppBskyEmbedImages.ViewImage[] images: AppBskyEmbedImages.ViewImage[]
index: number index: number
onPress?: EventFunction onPress?: (
index: number,
containerRef: AnimatedRef<React.Component<{}, {}, any>>,
) => void
onLongPress?: EventFunction onLongPress?: EventFunction
onPressIn?: EventFunction onPressIn?: EventFunction
imageStyle?: StyleProp<ImageStyle> imageStyle?: StyleProp<ImageStyle>
@@ -41,10 +45,11 @@ 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 (
<View style={a.flex_1}> <Animated.View style={a.flex_1} ref={containerRef}>
<Pressable <Pressable
onPress={onPress ? () => onPress(index) : undefined} onPress={onPress ? () => onPress(index, containerRef) : undefined}
onPressIn={onPressIn ? () => onPressIn(index) : undefined} onPressIn={onPressIn ? () => onPressIn(index) : undefined}
onLongPress={onLongPress ? () => onLongPress(index) : undefined} onLongPress={onLongPress ? () => onLongPress(index) : undefined}
style={[ style={[
@@ -95,6 +100,6 @@ export function GalleryItem({
</Text> </Text>
</View> </View>
) : null} ) : null}
</View> </Animated.View>
) )
} }
+9 -2
View File
@@ -1,5 +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 {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'
@@ -8,7 +9,10 @@ import {GalleryItem} from './Gallery'
interface ImageLayoutGridProps { interface ImageLayoutGridProps {
images: AppBskyEmbedImages.ViewImage[] images: AppBskyEmbedImages.ViewImage[]
onPress?: (index: number) => void onPress?: (
index: number,
containerRef: AnimatedRef<React.Component<{}, {}, any>>,
) => void
onLongPress?: (index: number) => void onLongPress?: (index: number) => void
onPressIn?: (index: number) => void onPressIn?: (index: number) => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
@@ -36,7 +40,10 @@ export function ImageLayoutGrid({style, ...props}: ImageLayoutGridProps) {
interface ImageLayoutGridInnerProps { interface ImageLayoutGridInnerProps {
images: AppBskyEmbedImages.ViewImage[] images: AppBskyEmbedImages.ViewImage[]
onPress?: (index: number) => void onPress?: (
index: number,
containerRef: AnimatedRef<React.Component<{}, {}, any>>,
) => void
onLongPress?: (index: number) => void onLongPress?: (index: number) => void
onPressIn?: (index: number) => void onPressIn?: (index: number) => void
viewContext?: PostEmbedViewContext viewContext?: PostEmbedViewContext
+28 -5
View File
@@ -6,6 +6,14 @@ import {
View, View,
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import Animated, {
AnimatedRef,
measure,
MeasuredDimensions,
runOnJS,
runOnUI,
useAnimatedRef,
} from 'react-native-reanimated'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import { import {
AppBskyEmbedExternal, AppBskyEmbedExternal,
@@ -61,6 +69,7 @@ export function PostEmbeds({
viewContext?: PostEmbedViewContext viewContext?: PostEmbedViewContext
}) { }) {
const {openLightbox} = useLightboxControls() const {openLightbox} = useLightboxControls()
const containerRef = useAnimatedRef()
// quote post with media // quote post with media
// = // =
@@ -138,13 +147,27 @@ export function PostEmbeds({
alt: img.alt, alt: img.alt,
aspectRatio: img.aspectRatio, aspectRatio: img.aspectRatio,
})) }))
const _openLightbox = (index: number) => { const _openLightbox = (
index: number,
thumbDims: MeasuredDimensions | null,
) => {
openLightbox({ openLightbox({
type: 'images', type: 'images',
images: items, images: items,
index, index,
thumbDims,
}) })
} }
const onPress = (
index: number,
ref: AnimatedRef<React.Component<{}, {}, any>>,
) => {
runOnUI(() => {
'worklet'
const dims = measure(ref)
runOnJS(_openLightbox)(index, dims)
})()
}
const onPressIn = (_: number) => { const onPressIn = (_: number) => {
InteractionManager.runAfterInteractions(() => { InteractionManager.runAfterInteractions(() => {
Image.prefetch(items.map(i => i.uri)) Image.prefetch(items.map(i => i.uri))
@@ -155,7 +178,7 @@ export function PostEmbeds({
const image = images[0] const image = images[0]
return ( return (
<ContentHider modui={moderation?.ui('contentMedia')}> <ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[a.mt_sm, style]}> <Animated.View ref={containerRef} style={[a.mt_sm, style]}>
<AutoSizedImage <AutoSizedImage
crop={ crop={
viewContext === PostEmbedViewContext.ThreadHighlighted viewContext === PostEmbedViewContext.ThreadHighlighted
@@ -166,13 +189,13 @@ export function PostEmbeds({
: 'constrained' : 'constrained'
} }
image={image} image={image}
onPress={() => _openLightbox(0)} onPress={() => onPress(0, containerRef)}
onPressIn={() => onPressIn(0)} onPressIn={() => onPressIn(0)}
hideBadge={ hideBadge={
viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
} }
/> />
</View> </Animated.View>
</ContentHider> </ContentHider>
) )
} }
@@ -182,7 +205,7 @@ export function PostEmbeds({
<View style={[a.mt_sm, style]}> <View style={[a.mt_sm, style]}>
<ImageLayoutGrid <ImageLayoutGrid
images={embed.images} images={embed.images}
onPress={_openLightbox} onPress={onPress}
onPressIn={onPressIn} onPressIn={onPressIn}
viewContext={viewContext} viewContext={viewContext}
/> />