diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index ac98deed7b..26e9406883 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -57,6 +57,7 @@ let ProfileHeaderShell = ({ openLightbox({ type: 'profile-image', profile: profile, + thumbDims: null, }) } }, [openLightbox, profile, moderation]) diff --git a/src/state/lightbox.tsx b/src/state/lightbox.tsx index 0760d2c965..eb5a88864f 100644 --- a/src/state/lightbox.tsx +++ b/src/state/lightbox.tsx @@ -1,4 +1,5 @@ import React from 'react' +import type {MeasuredDimensions} from 'react-native-reanimated' import {AppBskyActorDefs} from '@atproto/api' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' @@ -6,6 +7,7 @@ import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' type ProfileImageLightbox = { type: 'profile-image' profile: AppBskyActorDefs.ProfileViewDetailed + thumbDims: null } type ImagesLightboxItem = { @@ -17,6 +19,7 @@ type ImagesLightboxItem = { type ImagesLightbox = { type: 'images' images: ImagesLightboxItem[] + thumbDims: MeasuredDimensions | null index: number } diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index 1432b34ff8..0d0ac4df1f 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -11,6 +11,7 @@ import React, {ComponentType, useCallback, useMemo, useState} from 'react' import {Platform, StyleSheet, View} from 'react-native' import PagerView from 'react-native-pager-view' +import {MeasuredDimensions} from 'react-native-reanimated' import Animated, {useAnimatedStyle, withSpring} from 'react-native-reanimated' import {Edge, SafeAreaView} from 'react-native-safe-area-context' @@ -20,6 +21,7 @@ import ImageItem from './components/ImageItem/ImageItem' type Props = { images: ImageSource[] + thumbDims: MeasuredDimensions | null initialImageIndex: number visible: boolean onRequestClose: () => void @@ -32,6 +34,7 @@ const DEFAULT_BG_COLOR = '#000' function ImageViewing({ images, + thumbDims: _thumbDims, // TODO: Pass down and use for animation. initialImageIndex, visible, onRequestClose, diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/view/com/lightbox/Lightbox.tsx index a7f8fed771..891be3f9c9 100644 --- a/src/view/com/lightbox/Lightbox.tsx +++ b/src/view/com/lightbox/Lightbox.tsx @@ -35,6 +35,7 @@ export function Lightbox() { {uri: opts.profile.avatar || '', thumbUri: opts.profile.avatar || ''}, ]} initialImageIndex={0} + thumbDims={opts.thumbDims} visible onRequestClose={onClose} FooterComponent={LightboxFooter} @@ -46,6 +47,7 @@ export function Lightbox() { ({...img}))} initialImageIndex={opts.index} + thumbDims={opts.thumbDims} visible onRequestClose={onClose} FooterComponent={LightboxFooter} diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index 09f074e50a..785080c4be 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -74,6 +74,7 @@ export function ProfileSubpageHeader({ type: 'images', images: [{uri: avatar, thumbUri: avatar}], index: 0, + thumbDims: null, }) } }, [openLightbox, avatar]) diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx index e277b7e86a..d4d7d223d5 100644 --- a/src/view/com/util/images/Gallery.tsx +++ b/src/view/com/util/images/Gallery.tsx @@ -1,5 +1,6 @@ import React from 'react' import {Pressable, StyleProp, View, ViewStyle} from 'react-native' +import Animated, {AnimatedRef, useAnimatedRef} from 'react-native-reanimated' import {Image, ImageStyle} from 'expo-image' import {AppBskyEmbedImages} from '@atproto/api' import {msg} from '@lingui/macro' @@ -16,7 +17,10 @@ type EventFunction = (index: number) => void interface Props { images: AppBskyEmbedImages.ViewImage[] index: number - onPress?: EventFunction + onPress?: ( + index: number, + containerRef: AnimatedRef>, + ) => void onLongPress?: EventFunction onPressIn?: EventFunction imageStyle?: StyleProp @@ -41,10 +45,11 @@ export function GalleryItem({ const hasAlt = !!image.alt const hideBadges = viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia + const containerRef = useAnimatedRef() return ( - + onPress(index) : undefined} + onPress={onPress ? () => onPress(index, containerRef) : undefined} onPressIn={onPressIn ? () => onPressIn(index) : undefined} onLongPress={onLongPress ? () => onLongPress(index) : undefined} style={[ @@ -95,6 +100,6 @@ export function GalleryItem({ ) : null} - + ) } diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx index 7475a96c38..830040ba6c 100644 --- a/src/view/com/util/images/ImageLayoutGrid.tsx +++ b/src/view/com/util/images/ImageLayoutGrid.tsx @@ -1,5 +1,6 @@ import React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import {AnimatedRef} from 'react-native-reanimated' import {AppBskyEmbedImages} from '@atproto/api' import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types' @@ -8,7 +9,10 @@ import {GalleryItem} from './Gallery' interface ImageLayoutGridProps { images: AppBskyEmbedImages.ViewImage[] - onPress?: (index: number) => void + onPress?: ( + index: number, + containerRef: AnimatedRef>, + ) => void onLongPress?: (index: number) => void onPressIn?: (index: number) => void style?: StyleProp @@ -36,7 +40,10 @@ export function ImageLayoutGrid({style, ...props}: ImageLayoutGridProps) { interface ImageLayoutGridInnerProps { images: AppBskyEmbedImages.ViewImage[] - onPress?: (index: number) => void + onPress?: ( + index: number, + containerRef: AnimatedRef>, + ) => void onLongPress?: (index: number) => void onPressIn?: (index: number) => void viewContext?: PostEmbedViewContext diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 575b266942..19769bcd73 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -6,6 +6,14 @@ import { View, ViewStyle, } from 'react-native' +import Animated, { + AnimatedRef, + measure, + MeasuredDimensions, + runOnJS, + runOnUI, + useAnimatedRef, +} from 'react-native-reanimated' import {Image} from 'expo-image' import { AppBskyEmbedExternal, @@ -61,6 +69,7 @@ export function PostEmbeds({ viewContext?: PostEmbedViewContext }) { const {openLightbox} = useLightboxControls() + const containerRef = useAnimatedRef() // quote post with media // = @@ -138,13 +147,27 @@ export function PostEmbeds({ alt: img.alt, aspectRatio: img.aspectRatio, })) - const _openLightbox = (index: number) => { + const _openLightbox = ( + index: number, + thumbDims: MeasuredDimensions | null, + ) => { openLightbox({ type: 'images', images: items, index, + thumbDims, }) } + const onPress = ( + index: number, + ref: AnimatedRef>, + ) => { + runOnUI(() => { + 'worklet' + const dims = measure(ref) + runOnJS(_openLightbox)(index, dims) + })() + } const onPressIn = (_: number) => { InteractionManager.runAfterInteractions(() => { Image.prefetch(items.map(i => i.uri)) @@ -155,7 +178,7 @@ export function PostEmbeds({ const image = images[0] return ( - + _openLightbox(0)} + onPress={() => onPress(0, containerRef)} onPressIn={() => onPressIn(0)} hideBadge={ viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia } /> - + ) } @@ -182,7 +205,7 @@ export function PostEmbeds({