Measure tapped image coordinates before opening lightbox (#6001)
* Measure image on press * Pass dimensions to the lightbox component
This commit is contained in:
@@ -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])
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user