[Lightbox] Open animation (#6159)

* Measure all rects for embeds

* Measure avi rects too

* Animate lightbox in and out

* Account for safe area in the animation

* Tune spring times

* Remove null checks for measurements

* Remove superfluous view

* Block swipe while opening

* Interpolate width/height on native side for Android

* Make it fast by animating only affine transforms

* Fix tall image final state

The initial animation frame is still off on both platforms.

* Try to squeeze perf

* Avoid blank images during animation on iOS

* Fix bad rebase

* Fix a huge memory issue due to expo/expo#24894

* Fix last frame flash

* Fix thum dim calculation for tall images
This commit is contained in:
dan
2024-11-09 22:34:46 +00:00
committed by GitHub
parent e73d5c6c20
commit 2d73c5a24c
12 changed files with 629 additions and 192 deletions
+42 -21
View File
@@ -1,5 +1,12 @@
import React from 'react'
import {Pressable, StyleSheet, View} from 'react-native'
import Animated, {
measure,
MeasuredDimensions,
runOnJS,
runOnUI,
useAnimatedRef,
} from 'react-native-reanimated'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -53,6 +60,7 @@ export function ProfileSubpageHeader({
const {openLightbox} = useLightboxControls()
const pal = usePalette('default')
const canGoBack = navigation.canGoBack()
const aviRef = useAnimatedRef()
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
@@ -66,15 +74,14 @@ export function ProfileSubpageHeader({
setDrawerOpen(true)
}, [setDrawerOpen])
const onPressAvi = React.useCallback(() => {
if (
avatar // TODO && !(view.moderation.avatar.blur && view.moderation.avatar.noOverride)
) {
const _openLightbox = React.useCallback(
(uri: string, thumbRect: MeasuredDimensions | null) => {
openLightbox({
images: [
{
uri: avatar,
thumbUri: avatar,
uri,
thumbUri: uri,
thumbRect,
dimensions: {
// It's fine if it's actually smaller but we know it's 1:1.
height: 1000,
@@ -84,10 +91,22 @@ export function ProfileSubpageHeader({
},
],
index: 0,
thumbDims: null,
})
},
[openLightbox],
)
const onPressAvi = React.useCallback(() => {
if (
avatar // TODO && !(view.moderation.avatar.blur && view.moderation.avatar.noOverride)
) {
runOnUI(() => {
'worklet'
const rect = measure(aviRef)
runOnJS(_openLightbox)(avatar, rect)
})()
}
}, [openLightbox, avatar])
}, [_openLightbox, avatar, aviRef])
return (
<CenteredView style={pal.view}>
@@ -135,19 +154,21 @@ export function ProfileSubpageHeader({
paddingBottom: 6,
paddingHorizontal: isMobile ? 12 : 14,
}}>
<Pressable
testID="headerAviButton"
onPress={onPressAvi}
accessibilityRole="image"
accessibilityLabel={_(msg`View the avatar`)}
accessibilityHint=""
style={{width: 58}}>
{avatarType === 'starter-pack' ? (
<StarterPack width={58} gradient="sky" />
) : (
<UserAvatar type={avatarType} size={58} avatar={avatar} />
)}
</Pressable>
<Animated.View ref={aviRef} collapsable={false}>
<Pressable
testID="headerAviButton"
onPress={onPressAvi}
accessibilityRole="image"
accessibilityLabel={_(msg`View the avatar`)}
accessibilityHint=""
style={{width: 58}}>
{avatarType === 'starter-pack' ? (
<StarterPack width={58} gradient="sky" />
) : (
<UserAvatar type={avatarType} size={58} avatar={avatar} />
)}
</Pressable>
</Animated.View>
<View style={{flex: 1}}>
{isLoading ? (
<LoadingPlaceholder