about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/hooks/useHandleRef.ts39
-rw-r--r--src/screens/Profile/Header/Shell.tsx18
-rw-r--r--src/view/com/profile/ProfileSubpageHeader.tsx18
-rw-r--r--src/view/com/util/images/AutoSizedImage.tsx13
-rw-r--r--src/view/com/util/images/Gallery.tsx13
-rw-r--r--src/view/com/util/images/ImageLayoutGrid.tsx14
-rw-r--r--src/view/com/util/post-embeds/index.tsx14
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)
         })()
       }