diff options
author | dan <dan.abramov@gmail.com> | 2024-11-18 22:21:47 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-18 22:21:47 +0000 |
commit | 7b6c18272385494145676e42cbe922bde7ceae6b (patch) | |
tree | c8d41082b9a04e33e3b24b4d51e5af25cfd63daa | |
parent | 4f0f9eb4139a8c3439be0125ffd982b3cbf5c9b5 (diff) | |
download | voidsky-7b6c18272385494145676e42cbe922bde7ceae6b.tar.zst |
Add useHandleRef as a lighter alternative for useAnimatedRef (#6500)
-rw-r--r-- | src/lib/hooks/useHandleRef.ts | 39 | ||||
-rw-r--r-- | src/screens/Profile/Header/Shell.tsx | 18 | ||||
-rw-r--r-- | src/view/com/profile/ProfileSubpageHeader.tsx | 18 | ||||
-rw-r--r-- | src/view/com/util/images/AutoSizedImage.tsx | 13 | ||||
-rw-r--r-- | src/view/com/util/images/Gallery.tsx | 13 | ||||
-rw-r--r-- | src/view/com/util/images/ImageLayoutGrid.tsx | 14 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/index.tsx | 14 |
7 files changed, 75 insertions, 54 deletions
diff --git a/src/lib/hooks/useHandleRef.ts b/src/lib/hooks/useHandleRef.ts new file mode 100644 index 000000000..167ba270b --- /dev/null +++ b/src/lib/hooks/useHandleRef.ts @@ -0,0 +1,39 @@ +import {useState} from 'react' +import {AnimatedRef, measure, MeasuredDimensions} from 'react-native-reanimated' + +export type HandleRef = { + (node: any): void + current: null | number +} + +// This is a lighterweight alternative to `useAnimatedRef()` for imperative UI thread actions. +// Render it like <View ref={ref} />, then pass `ref.current` to `measureHandle()` and such. +export function useHandleRef(): HandleRef { + return useState(() => { + const ref = (node: any) => { + if (node) { + ref.current = + node._nativeTag ?? + node.__nativeTag ?? + node.canonical?.nativeTag ?? + null + } else { + ref.current = null + } + } + ref.current = null + return ref + })[0] as HandleRef +} + +// When using this version, you need to read ref.current on the JS thread, and pass it to UI. +export function measureHandle( + current: number | null, +): MeasuredDimensions | null { + 'worklet' + if (current !== null) { + return measure((() => current) as AnimatedRef<any>) + } else { + return null + } +} diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index 1a1e7d4a2..573d38145 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -1,12 +1,6 @@ import React, {memo} from 'react' import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' -import Animated, { - measure, - MeasuredDimensions, - runOnJS, - runOnUI, - useAnimatedRef, -} from 'react-native-reanimated' +import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated' import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' @@ -14,6 +8,7 @@ import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {BACK_HITSLOP} from '#/lib/constants' +import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {NavigationProp} from '#/lib/routes/types' import {isIOS} from '#/platform/detection' @@ -49,7 +44,7 @@ let ProfileHeaderShell = ({ const {openLightbox} = useLightboxControls() const navigation = useNavigation<NavigationProp>() const {isDesktop} = useWebMediaQueries() - const aviRef = useAnimatedRef() + const aviRef = useHandleRef() const onPressBack = React.useCallback(() => { if (navigation.canGoBack()) { @@ -86,9 +81,10 @@ let ProfileHeaderShell = ({ const modui = moderation.ui('avatar') const avatar = profile.avatar if (avatar && !(modui.blur && modui.noOverride)) { + const aviHandle = aviRef.current runOnUI(() => { 'worklet' - const rect = measure(aviRef) + const rect = measureHandle(aviHandle) runOnJS(_openLightbox)(avatar, rect) })() } @@ -170,14 +166,14 @@ let ProfileHeaderShell = ({ styles.avi, profile.associated?.labeler && styles.aviLabeler, ]}> - <Animated.View ref={aviRef} collapsable={false}> + <View ref={aviRef} collapsable={false}> <UserAvatar type={profile.associated?.labeler ? 'labeler' : 'user'} size={90} avatar={profile.avatar} moderation={moderation.ui('avatar')} /> - </Animated.View> + </View> </View> </TouchableWithoutFeedback> </GrowableAvatar> diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index d73b322f2..0e25fe5e6 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -1,18 +1,13 @@ import React from 'react' import {Pressable, StyleSheet, View} from 'react-native' -import Animated, { - measure, - MeasuredDimensions, - runOnJS, - runOnUI, - useAnimatedRef, -} from 'react-native-reanimated' +import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {BACK_HITSLOP} from '#/lib/constants' +import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef' import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {makeProfileLink} from '#/lib/routes/links' @@ -60,7 +55,7 @@ export function ProfileSubpageHeader({ const {openLightbox} = useLightboxControls() const pal = usePalette('default') const canGoBack = navigation.canGoBack() - const aviRef = useAnimatedRef() + const aviRef = useHandleRef() const onPressBack = React.useCallback(() => { if (navigation.canGoBack()) { @@ -101,9 +96,10 @@ export function ProfileSubpageHeader({ if ( avatar // TODO && !(view.moderation.avatar.blur && view.moderation.avatar.noOverride) ) { + const aviHandle = aviRef.current runOnUI(() => { 'worklet' - const rect = measure(aviRef) + const rect = measureHandle(aviHandle) runOnJS(_openLightbox)(avatar, rect) })() } @@ -155,7 +151,7 @@ export function ProfileSubpageHeader({ paddingBottom: 6, paddingHorizontal: isMobile ? 12 : 14, }}> - <Animated.View ref={aviRef} collapsable={false}> + <View ref={aviRef} collapsable={false}> <Pressable testID="headerAviButton" onPress={onPressAvi} @@ -169,7 +165,7 @@ export function ProfileSubpageHeader({ <UserAvatar type={avatarType} size={58} avatar={avatar} /> )} </Pressable> - </Animated.View> + </View> <View style={{flex: 1}}> {isLoading ? ( <LoadingPlaceholder diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index 0ecbb3597..617b9bec4 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -1,11 +1,11 @@ import React, {useRef} from 'react' import {DimensionValue, Pressable, View} from 'react-native' -import Animated, {AnimatedRef, useAnimatedRef} from 'react-native-reanimated' import {Image} from 'expo-image' import {AppBskyEmbedImages} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {HandleRef, useHandleRef} from '#/lib/hooks/useHandleRef' import type {Dimensions} from '#/lib/media/types' import {isNative} from '#/platform/detection' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' @@ -68,17 +68,14 @@ export function AutoSizedImage({ image: AppBskyEmbedImages.ViewImage crop?: 'none' | 'square' | 'constrained' hideBadge?: boolean - onPress?: ( - containerRef: AnimatedRef<React.Component<{}, {}, any>>, - fetchedDims: Dimensions | null, - ) => void + onPress?: (containerRef: HandleRef, fetchedDims: Dimensions | null) => void onLongPress?: () => void onPressIn?: () => void }) { const t = useTheme() const {_} = useLingui() const largeAlt = useLargeAltBadgeEnabled() - const containerRef = useAnimatedRef() + const containerRef = useHandleRef() const fetchedDimsRef = useRef<{width: number; height: number} | null>(null) let aspectRatio: number | undefined @@ -109,7 +106,7 @@ export function AutoSizedImage({ const hasAlt = !!image.alt const contents = ( - <Animated.View ref={containerRef} collapsable={false} style={{flex: 1}}> + <View ref={containerRef} collapsable={false} style={{flex: 1}}> <Image style={[a.w_full, a.h_full]} source={image.thumb} @@ -188,7 +185,7 @@ export function AutoSizedImage({ )} </View> ) : null} - </Animated.View> + </View> ) if (cropDisabled) { diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx index 9d0817bd2..cc3eda68d 100644 --- a/src/view/com/util/images/Gallery.tsx +++ b/src/view/com/util/images/Gallery.tsx @@ -1,11 +1,11 @@ import React from 'react' import {Pressable, StyleProp, View, ViewStyle} from 'react-native' -import Animated, {AnimatedRef} from 'react-native-reanimated' import {Image, ImageStyle} from 'expo-image' import {AppBskyEmbedImages} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {HandleRef} from '#/lib/hooks/useHandleRef' import {Dimensions} from '#/lib/media/types' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types' @@ -20,7 +20,7 @@ interface Props { index: number onPress?: ( index: number, - containerRefs: AnimatedRef<React.Component<{}, {}, any>>[], + containerRefs: HandleRef[], fetchedDims: (Dimensions | null)[], ) => void onLongPress?: EventFunction @@ -28,7 +28,7 @@ interface Props { imageStyle?: StyleProp<ImageStyle> viewContext?: PostEmbedViewContext insetBorderStyle?: StyleProp<ViewStyle> - containerRefs: AnimatedRef<React.Component<{}, {}, any>>[] + containerRefs: HandleRef[] thumbDimsRef: React.MutableRefObject<(Dimensions | null)[]> } @@ -52,10 +52,7 @@ export function GalleryItem({ const hideBadges = viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia return ( - <Animated.View - style={a.flex_1} - ref={containerRefs[index]} - collapsable={false}> + <View style={a.flex_1} ref={containerRefs[index]} collapsable={false}> <Pressable onPress={ onPress @@ -118,6 +115,6 @@ export function GalleryItem({ </Text> </View> ) : null} - </Animated.View> + </View> ) } diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx index dcc330dac..16ea9d453 100644 --- a/src/view/com/util/images/ImageLayoutGrid.tsx +++ b/src/view/com/util/images/ImageLayoutGrid.tsx @@ -1,8 +1,8 @@ import React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' -import {AnimatedRef, useAnimatedRef} from 'react-native-reanimated' import {AppBskyEmbedImages} from '@atproto/api' +import {HandleRef, useHandleRef} from '#/lib/hooks/useHandleRef' import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types' import {atoms as a, useBreakpoints} from '#/alf' import {Dimensions} from '../../lightbox/ImageViewing/@types' @@ -12,7 +12,7 @@ interface ImageLayoutGridProps { images: AppBskyEmbedImages.ViewImage[] onPress?: ( index: number, - containerRefs: AnimatedRef<React.Component<{}, {}, any>>[], + containerRefs: HandleRef[], fetchedDims: (Dimensions | null)[], ) => void onLongPress?: (index: number) => void @@ -43,7 +43,7 @@ interface ImageLayoutGridInnerProps { images: AppBskyEmbedImages.ViewImage[] onPress?: ( index: number, - containerRefs: AnimatedRef<React.Component<{}, {}, any>>[], + containerRefs: HandleRef[], fetchedDims: (Dimensions | null)[], ) => void onLongPress?: (index: number) => void @@ -56,10 +56,10 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) { const gap = props.gap const count = props.images.length - const containerRef1 = useAnimatedRef() - const containerRef2 = useAnimatedRef() - const containerRef3 = useAnimatedRef() - const containerRef4 = useAnimatedRef() + const containerRef1 = useHandleRef() + const containerRef2 = useHandleRef() + const containerRef3 = useHandleRef() + const containerRef4 = useHandleRef() const thumbDimsRef = React.useRef<(Dimensions | null)[]>([]) switch (count) { diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 1351a2cbc..9dc43da8e 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -6,13 +6,7 @@ import { View, ViewStyle, } from 'react-native' -import { - AnimatedRef, - measure, - MeasuredDimensions, - runOnJS, - runOnUI, -} from 'react-native-reanimated' +import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated' import {Image} from 'expo-image' import { AppBskyEmbedExternal, @@ -27,6 +21,7 @@ import { ModerationDecision, } from '@atproto/api' +import {HandleRef, measureHandle} from '#/lib/hooks/useHandleRef' import {usePalette} from '#/lib/hooks/usePalette' import {useLightboxControls} from '#/state/lightbox' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -163,12 +158,13 @@ export function PostEmbeds({ } const onPress = ( index: number, - refs: AnimatedRef<React.Component<{}, {}, any>>[], + refs: HandleRef[], fetchedDims: (Dimensions | null)[], ) => { + const handles = refs.map(r => r.current) runOnUI(() => { 'worklet' - const rects = refs.map(ref => (ref ? measure(ref) : null)) + const rects = handles.map(measureHandle) runOnJS(_openLightbox)(index, rects, fetchedDims) })() } |