Bluesky app fork with some witchin' additions 💫

Use compiler-safe Reanimated get/set APIs (#6391)

* Convert lightbox to get/set

* Work around software-mansion/react-native-reanimated#6613

* Use get/set in more places

* Port MainScrollProvider to get/set

* Port more to get/set

* Port composer to get/set

* Remove unnecessary thread hops in composer

* Port more things to get/set

* Convert more to get/set, remove redundant runOnJS

* Convert remaining cases to get/set

authored by danabra.mov and committed by

GitHub 474c4eff d575a2fd

+351 -304
+40 -32
src/Splash.tsx
··· 81 81 return { 82 82 transform: [ 83 83 { 84 - scale: interpolate(intro.value, [0, 1], [0.8, 1], 'clamp'), 84 + scale: interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp'), 85 85 }, 86 86 { 87 87 scale: interpolate( 88 - outroLogo.value, 88 + outroLogo.get(), 89 89 [0, 0.08, 1], 90 90 [1, 0.8, 500], 91 91 'clamp', 92 92 ), 93 93 }, 94 94 ], 95 - opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'), 95 + opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 96 96 } 97 97 }) 98 98 const bottomLogoAnimation = useAnimatedStyle(() => { 99 99 return { 100 - opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'), 100 + opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 101 101 } 102 102 }) 103 103 const reducedLogoAnimation = useAnimatedStyle(() => { 104 104 return { 105 105 transform: [ 106 106 { 107 - scale: interpolate(intro.value, [0, 1], [0.8, 1], 'clamp'), 107 + scale: interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp'), 108 108 }, 109 109 ], 110 - opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'), 110 + opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'), 111 111 } 112 112 }) 113 113 114 114 const logoWrapperAnimation = useAnimatedStyle(() => { 115 115 return { 116 116 opacity: interpolate( 117 - outroAppOpacity.value, 117 + outroAppOpacity.get(), 118 118 [0, 0.1, 0.2, 1], 119 119 [1, 1, 0, 0], 120 120 'clamp', ··· 126 126 return { 127 127 transform: [ 128 128 { 129 - scale: interpolate(outroApp.value, [0, 1], [1.1, 1], 'clamp'), 129 + scale: interpolate(outroApp.get(), [0, 1], [1.1, 1], 'clamp'), 130 130 }, 131 131 ], 132 132 opacity: interpolate( 133 - outroAppOpacity.value, 133 + outroAppOpacity.get(), 134 134 [0, 0.1, 0.2, 1], 135 135 [0, 0, 1, 1], 136 136 'clamp', ··· 146 146 if (isReady) { 147 147 SplashScreen.hideAsync() 148 148 .then(() => { 149 - intro.value = withTiming( 150 - 1, 151 - {duration: 400, easing: Easing.out(Easing.cubic)}, 152 - async () => { 153 - // set these values to check animation at specific point 154 - // outroLogo.value = 0.1 155 - // outroApp.value = 0.1 156 - outroLogo.value = withTiming( 157 - 1, 158 - {duration: 1200, easing: Easing.in(Easing.cubic)}, 159 - () => { 160 - runOnJS(onFinish)() 161 - }, 162 - ) 163 - outroApp.value = withTiming(1, { 164 - duration: 1200, 165 - easing: Easing.inOut(Easing.cubic), 166 - }) 167 - outroAppOpacity.value = withTiming(1, { 168 - duration: 1200, 169 - easing: Easing.in(Easing.cubic), 170 - }) 171 - }, 149 + intro.set(() => 150 + withTiming( 151 + 1, 152 + {duration: 400, easing: Easing.out(Easing.cubic)}, 153 + async () => { 154 + // set these values to check animation at specific point 155 + // outroLogo.set(0.1) 156 + // outroApp.set(0.1) 157 + outroLogo.set(() => 158 + withTiming( 159 + 1, 160 + {duration: 1200, easing: Easing.in(Easing.cubic)}, 161 + () => { 162 + runOnJS(onFinish)() 163 + }, 164 + ), 165 + ) 166 + outroApp.set(() => 167 + withTiming(1, { 168 + duration: 1200, 169 + easing: Easing.inOut(Easing.cubic), 170 + }), 171 + ) 172 + outroAppOpacity.set(() => 173 + withTiming(1, { 174 + duration: 1200, 175 + easing: Easing.in(Easing.cubic), 176 + }), 177 + ) 178 + }, 179 + ), 172 180 ) 173 181 }) 174 182 .catch(() => {})
+3 -4
src/components/Loader.tsx
··· 17 17 const rotation = useSharedValue(0) 18 18 19 19 const animatedStyles = useAnimatedStyle(() => ({ 20 - transform: [{rotate: rotation.value + 'deg'}], 20 + transform: [{rotate: rotation.get() + 'deg'}], 21 21 })) 22 22 23 23 React.useEffect(() => { 24 - rotation.value = withRepeat( 25 - withTiming(360, {duration: 500, easing: Easing.linear}), 26 - -1, 24 + rotation.set(() => 25 + withRepeat(withTiming(360, {duration: 500, easing: Easing.linear}), -1), 27 26 ) 28 27 }, [rotation]) 29 28
+27 -21
src/components/ProgressGuide/Toast.tsx
··· 55 55 56 56 // animate the opacity then set isOpen to false when done 57 57 const setIsntOpen = () => setIsOpen(false) 58 - opacity.value = withTiming( 59 - 0, 60 - { 61 - duration: 400, 62 - easing: Easing.out(Easing.cubic), 63 - }, 64 - () => runOnJS(setIsntOpen)(), 58 + opacity.set(() => 59 + withTiming( 60 + 0, 61 + { 62 + duration: 400, 63 + easing: Easing.out(Easing.cubic), 64 + }, 65 + () => runOnJS(setIsntOpen)(), 66 + ), 65 67 ) 66 68 }, [setIsOpen, opacity]) 67 69 ··· 71 73 72 74 // animate the vertical translation, the opacity, and the checkmark 73 75 const playCheckmark = () => animatedCheckRef.current?.play() 74 - opacity.value = 0 75 - opacity.value = withTiming( 76 - 1, 77 - { 78 - duration: 100, 76 + opacity.set(0) 77 + opacity.set(() => 78 + withTiming( 79 + 1, 80 + { 81 + duration: 100, 82 + easing: Easing.out(Easing.cubic), 83 + }, 84 + () => runOnJS(playCheckmark)(), 85 + ), 86 + ) 87 + translateY.set(0) 88 + translateY.set(() => 89 + withTiming(insets.top + 10, { 90 + duration: 500, 79 91 easing: Easing.out(Easing.cubic), 80 - }, 81 - () => runOnJS(playCheckmark)(), 92 + }), 82 93 ) 83 - translateY.value = 0 84 - translateY.value = withTiming(insets.top + 10, { 85 - duration: 500, 86 - easing: Easing.out(Easing.cubic), 87 - }) 88 94 89 95 // start the countdown timer to autoclose 90 96 timeoutRef.current = setTimeout(close, visibleDuration || 5e3) ··· 114 120 }, [winDim.width]) 115 121 116 122 const animatedStyle = useAnimatedStyle(() => ({ 117 - transform: [{translateY: translateY.value}], 118 - opacity: opacity.value, 123 + transform: [{translateY: translateY.get()}], 124 + opacity: opacity.get(), 119 125 })) 120 126 121 127 return (
+12 -8
src/components/anim/AnimatedCheck.tsx
··· 32 32 const checkAnim = useSharedValue(0) 33 33 34 34 const circleAnimatedProps = useAnimatedProps(() => ({ 35 - strokeDashoffset: 166 - circleAnim.value * 166, 35 + strokeDashoffset: 166 - circleAnim.get() * 166, 36 36 })) 37 37 const checkAnimatedProps = useAnimatedProps(() => ({ 38 - strokeDashoffset: 48 - 48 * checkAnim.value, 38 + strokeDashoffset: 48 - 48 * checkAnim.get(), 39 39 })) 40 40 41 41 const play = React.useCallback( 42 42 (cb?: () => void) => { 43 - circleAnim.value = 0 44 - checkAnim.value = 0 43 + circleAnim.set(0) 44 + checkAnim.set(0) 45 45 46 - circleAnim.value = withTiming(1, {duration: 500, easing: Easing.linear}) 47 - checkAnim.value = withDelay( 48 - 500, 49 - withTiming(1, {duration: 300, easing: Easing.linear}, cb), 46 + circleAnim.set(() => 47 + withTiming(1, {duration: 500, easing: Easing.linear}), 48 + ) 49 + checkAnim.set(() => 50 + withDelay( 51 + 500, 52 + withTiming(1, {duration: 300, easing: Easing.linear}, cb), 53 + ), 50 54 ) 51 55 }, 52 56 [circleAnim, checkAnim],
+9 -7
src/components/dms/ActionsWrapper.tsx
··· 34 34 const scale = useSharedValue(1) 35 35 36 36 const animatedStyle = useAnimatedStyle(() => ({ 37 - transform: [{scale: scale.value}], 37 + transform: [{scale: scale.get()}], 38 38 })) 39 39 40 40 const open = React.useCallback(() => { ··· 46 46 const shrink = React.useCallback(() => { 47 47 'worklet' 48 48 cancelAnimation(scale) 49 - scale.value = withTiming(1, {duration: 200}) 49 + scale.set(() => withTiming(1, {duration: 200})) 50 50 }, [scale]) 51 51 52 52 const doubleTapGesture = Gesture.Tap() ··· 58 58 const pressAndHoldGesture = Gesture.LongPress() 59 59 .onStart(() => { 60 60 'worklet' 61 - scale.value = withTiming(1.05, {duration: 200}, finished => { 62 - if (!finished) return 63 - runOnJS(open)() 64 - shrink() 65 - }) 61 + scale.set(() => 62 + withTiming(1.05, {duration: 200}, finished => { 63 + if (!finished) return 64 + runOnJS(open)() 65 + shrink() 66 + }), 67 + ) 66 68 }) 67 69 .onTouchesUp(shrink) 68 70 .onTouchesMove(shrink)
+3 -3
src/components/dms/ChatEmptyPill.tsx
··· 42 42 43 43 const onPressIn = React.useCallback(() => { 44 44 if (isWeb) return 45 - scale.value = withTiming(1.075, {duration: 100}) 45 + scale.set(() => withTiming(1.075, {duration: 100})) 46 46 }, [scale]) 47 47 48 48 const onPressOut = React.useCallback(() => { 49 49 if (isWeb) return 50 - scale.value = withTiming(1, {duration: 100}) 50 + scale.set(() => withTiming(1, {duration: 100})) 51 51 }, [scale]) 52 52 53 53 const onPress = React.useCallback(() => { ··· 61 61 }, [playHaptic, prompts.length]) 62 62 63 63 const animatedStyle = useAnimatedStyle(() => ({ 64 - transform: [{scale: scale.value}], 64 + transform: [{scale: scale.get()}], 65 65 })) 66 66 67 67 return (
+3 -3
src/components/dms/NewMessagesPill.tsx
··· 35 35 36 36 const onPressIn = React.useCallback(() => { 37 37 if (isWeb) return 38 - scale.value = withTiming(1.075, {duration: 100}) 38 + scale.set(() => withTiming(1.075, {duration: 100})) 39 39 }, [scale]) 40 40 41 41 const onPressOut = React.useCallback(() => { 42 42 if (isWeb) return 43 - scale.value = withTiming(1, {duration: 100}) 43 + scale.set(() => withTiming(1, {duration: 100})) 44 44 }, [scale]) 45 45 46 46 const onPress = React.useCallback(() => { ··· 49 49 }, [onPressInner, playHaptic]) 50 50 51 51 const animatedStyle = useAnimatedStyle(() => ({ 52 - transform: [{scale: scale.value}], 52 + transform: [{scale: scale.get()}], 53 53 })) 54 54 55 55 return (
+43 -41
src/lib/custom-animations/GestureActionView.tsx
··· 61 61 const clampedTransX = useDerivedValue(() => { 62 62 const min = actions.leftFirst ? -MAX_WIDTH : 0 63 63 const max = actions.rightFirst ? MAX_WIDTH : 0 64 - return clamp(transX.value, min, max) 64 + return clamp(transX.get(), min, max) 65 65 }) 66 66 67 67 const iconScale = useSharedValue(1) ··· 75 75 return 76 76 } 77 77 78 - iconScale.value = withSequence( 79 - withTiming(1.2, {duration: 175}), 80 - withTiming(1, {duration: 100}), 78 + iconScale.set(() => 79 + withSequence( 80 + withTiming(1.2, {duration: 175}), 81 + withTiming(1, {duration: 100}), 82 + ), 81 83 ) 82 84 } 83 85 84 86 useAnimatedReaction( 85 87 () => transX, 86 88 () => { 87 - if (transX.value === 0) { 89 + if (transX.get() === 0) { 88 90 runOnJS(setActiveAction)(null) 89 - } else if (transX.value < 0) { 91 + } else if (transX.get() < 0) { 90 92 if ( 91 93 actions.leftSecond && 92 - transX.value <= -actions.leftSecond.threshold 94 + transX.get() <= -actions.leftSecond.threshold 93 95 ) { 94 96 if (activeAction !== 'leftSecond') { 95 97 runOnJS(setActiveAction)('leftSecond') ··· 97 99 } else if (activeAction !== 'leftFirst') { 98 100 runOnJS(setActiveAction)('leftFirst') 99 101 } 100 - } else if (transX.value > 0) { 102 + } else if (transX.get() > 0) { 101 103 if ( 102 104 actions.rightSecond && 103 - transX.value > actions.rightSecond.threshold 105 + transX.get() > actions.rightSecond.threshold 104 106 ) { 105 107 if (activeAction !== 'rightSecond') { 106 108 runOnJS(setActiveAction)('rightSecond') ··· 119 121 .activeOffsetY([-200, 200]) 120 122 .onStart(() => { 121 123 'worklet' 122 - isActive.value = true 124 + isActive.set(true) 123 125 }) 124 126 .onChange(e => { 125 127 'worklet' 126 - transX.value = e.translationX 128 + transX.set(e.translationX) 127 129 128 130 if (e.translationX < 0) { 129 131 // Left side 130 132 if (actions.leftSecond) { 131 133 if ( 132 134 e.translationX <= -actions.leftSecond.threshold && 133 - !hitSecond.value 135 + !hitSecond.get() 134 136 ) { 135 137 runPopAnimation() 136 138 runOnJS(haptic)() 137 - hitSecond.value = true 139 + hitSecond.set(true) 138 140 } else if ( 139 - hitSecond.value && 141 + hitSecond.get() && 140 142 e.translationX > -actions.leftSecond.threshold 141 143 ) { 142 144 runPopAnimation() 143 - hitSecond.value = false 145 + hitSecond.set(false) 144 146 } 145 147 } 146 148 147 - if (!hitSecond.value && actions.leftFirst) { 149 + if (!hitSecond.get() && actions.leftFirst) { 148 150 if ( 149 151 e.translationX <= -actions.leftFirst.threshold && 150 - !hitFirst.value 152 + !hitFirst.get() 151 153 ) { 152 154 runPopAnimation() 153 155 runOnJS(haptic)() 154 - hitFirst.value = true 156 + hitFirst.set(true) 155 157 } else if ( 156 - hitFirst.value && 158 + hitFirst.get() && 157 159 e.translationX > -actions.leftFirst.threshold 158 160 ) { 159 - hitFirst.value = false 161 + hitFirst.set(false) 160 162 } 161 163 } 162 164 } else if (e.translationX > 0) { ··· 164 166 if (actions.rightSecond) { 165 167 if ( 166 168 e.translationX >= actions.rightSecond.threshold && 167 - !hitSecond.value 169 + !hitSecond.get() 168 170 ) { 169 171 runPopAnimation() 170 172 runOnJS(haptic)() 171 - hitSecond.value = true 173 + hitSecond.set(true) 172 174 } else if ( 173 - hitSecond.value && 175 + hitSecond.get() && 174 176 e.translationX < actions.rightSecond.threshold 175 177 ) { 176 178 runPopAnimation() 177 - hitSecond.value = false 179 + hitSecond.set(false) 178 180 } 179 181 } 180 182 181 - if (!hitSecond.value && actions.rightFirst) { 183 + if (!hitSecond.get() && actions.rightFirst) { 182 184 if ( 183 185 e.translationX >= actions.rightFirst.threshold && 184 - !hitFirst.value 186 + !hitFirst.get() 185 187 ) { 186 188 runPopAnimation() 187 189 runOnJS(haptic)() 188 - hitFirst.value = true 190 + hitFirst.set(true) 189 191 } else if ( 190 - hitFirst.value && 192 + hitFirst.get() && 191 193 e.translationX < actions.rightFirst.threshold 192 194 ) { 193 - hitFirst.value = false 195 + hitFirst.set(false) 194 196 } 195 197 } 196 198 } ··· 198 200 .onEnd(e => { 199 201 'worklet' 200 202 if (e.translationX < 0) { 201 - if (hitSecond.value && actions.leftSecond) { 203 + if (hitSecond.get() && actions.leftSecond) { 202 204 runOnJS(actions.leftSecond.action)() 203 - } else if (hitFirst.value && actions.leftFirst) { 205 + } else if (hitFirst.get() && actions.leftFirst) { 204 206 runOnJS(actions.leftFirst.action)() 205 207 } 206 208 } else if (e.translationX > 0) { 207 - if (hitSecond.value && actions.rightSecond) { 209 + if (hitSecond.get() && actions.rightSecond) { 208 210 runOnJS(actions.rightSecond.action)() 209 - } else if (hitSecond.value && actions.rightFirst) { 211 + } else if (hitSecond.get() && actions.rightFirst) { 210 212 runOnJS(actions.rightFirst.action)() 211 213 } 212 214 } 213 - transX.value = withTiming(0, {duration: 200}) 214 - hitFirst.value = false 215 - hitSecond.value = false 216 - isActive.value = false 215 + transX.set(() => withTiming(0, {duration: 200})) 216 + hitFirst.set(false) 217 + hitSecond.set(false) 218 + isActive.set(false) 217 219 }) 218 220 219 221 const composedGesture = Gesture.Simultaneous(panGesture) 220 222 221 223 const animatedSliderStyle = useAnimatedStyle(() => { 222 224 return { 223 - transform: [{translateX: clampedTransX.value}], 225 + transform: [{translateX: clampedTransX.get()}], 224 226 } 225 227 }) 226 228 ··· 274 276 const animatedBackgroundStyle = useAnimatedStyle(() => { 275 277 return { 276 278 backgroundColor: interpolateColor( 277 - clampedTransX.value, 279 + clampedTransX.get(), 278 280 interpolation.inputRange, 279 281 // @ts-expect-error - Weird type expected by reanimated, but this is okay 280 282 interpolation.outputRange, ··· 283 285 }) 284 286 285 287 const animatedIconStyle = useAnimatedStyle(() => { 286 - const absTransX = Math.abs(clampedTransX.value) 288 + const absTransX = Math.abs(clampedTransX.get()) 287 289 return { 288 290 opacity: interpolate(absTransX, [0, 75], [0.15, 1]), 289 - transform: [{scale: iconScale.value}], 291 + transform: [{scale: iconScale.get()}], 290 292 } 291 293 }) 292 294
+5 -8
src/lib/custom-animations/PressableScale.tsx
··· 2 2 import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native' 3 3 import Animated, { 4 4 cancelAnimation, 5 - runOnJS, 6 5 useAnimatedStyle, 7 6 useReducedMotion, 8 7 useSharedValue, ··· 32 31 const scale = useSharedValue(1) 33 32 34 33 const animatedStyle = useAnimatedStyle(() => ({ 35 - transform: [{scale: scale.value}], 34 + transform: [{scale: scale.get()}], 36 35 })) 37 36 38 37 return ( 39 38 <AnimatedPressable 40 39 accessibilityRole="button" 41 40 onPressIn={e => { 42 - 'worklet' 43 41 if (onPressIn) { 44 - runOnJS(onPressIn)(e) 42 + onPressIn(e) 45 43 } 46 44 cancelAnimation(scale) 47 - scale.value = withTiming(targetScale, {duration: 100}) 45 + scale.set(() => withTiming(targetScale, {duration: 100})) 48 46 }} 49 47 onPressOut={e => { 50 - 'worklet' 51 48 if (onPressOut) { 52 - runOnJS(onPressOut)(e) 49 + onPressOut(e) 53 50 } 54 51 cancelAnimation(scale) 55 - scale.value = withTiming(1, {duration: 100}) 52 + scale.set(() => withTiming(1, {duration: 100})) 56 53 }} 57 54 style={[!reducedMotion && animatedStyle, style]} 58 55 {...rest}>
+11 -9
src/lib/hooks/useMinimalShellTransform.ts
··· 10 10 const {headerHeight} = useShellLayout() 11 11 12 12 const headerTransform = useAnimatedStyle(() => { 13 + const headerModeValue = headerMode.get() 13 14 return { 14 - pointerEvents: headerMode.value === 0 ? 'auto' : 'none', 15 - opacity: Math.pow(1 - headerMode.value, 2), 15 + pointerEvents: headerModeValue === 0 ? 'auto' : 'none', 16 + opacity: Math.pow(1 - headerModeValue, 2), 16 17 transform: [ 17 18 { 18 19 translateY: interpolate( 19 - headerMode.value, 20 + headerModeValue, 20 21 [0, 1], 21 - [0, -headerHeight.value], 22 + [0, -headerHeight.get()], 22 23 ), 23 24 }, 24 25 ], ··· 33 34 const {footerHeight} = useShellLayout() 34 35 35 36 const footerTransform = useAnimatedStyle(() => { 37 + const footerModeValue = footerMode.get() 36 38 return { 37 - pointerEvents: footerMode.value === 0 ? 'auto' : 'none', 38 - opacity: Math.pow(1 - footerMode.value, 2), 39 + pointerEvents: footerModeValue === 0 ? 'auto' : 'none', 40 + opacity: Math.pow(1 - footerModeValue, 2), 39 41 transform: [ 40 42 { 41 43 translateY: interpolate( 42 - footerMode.value, 44 + footerModeValue, 43 45 [0, 1], 44 - [0, footerHeight.value], 46 + [0, footerHeight.get()], 45 47 ), 46 48 }, 47 49 ], ··· 58 60 return { 59 61 transform: [ 60 62 { 61 - translateY: interpolate(footerMode.value, [0, 1], [-44, 0]), 63 + translateY: interpolate(footerMode.get(), [0, 1], [-44, 0]), 62 64 }, 63 65 ], 64 66 }
+5 -5
src/screens/Messages/components/MessageInput.tsx
··· 108 108 const measurement = measure(inputRef) 109 109 if (!measurement) return 110 110 111 - const max = windowHeight - -keyboardHeight.value - topInset - 150 111 + const max = windowHeight - -keyboardHeight.get() - topInset - 150 112 112 const availableSpace = max - measurement.height 113 113 114 - maxHeight.value = max 115 - isInputScrollable.value = availableSpace < 30 114 + maxHeight.set(max) 115 + isInputScrollable.set(availableSpace < 30) 116 116 }, 117 117 }, 118 118 [windowHeight, topInset], 119 119 ) 120 120 121 121 const animatedStyle = useAnimatedStyle(() => ({ 122 - maxHeight: maxHeight.value, 122 + maxHeight: maxHeight.get(), 123 123 })) 124 124 125 125 const animatedProps = useAnimatedProps(() => ({ 126 - scrollEnabled: isInputScrollable.value, 126 + scrollEnabled: isInputScrollable.get(), 127 127 })) 128 128 129 129 return (
+18 -18
src/screens/Messages/components/MessagesList.tsx
··· 145 145 (_: number, height: number) => { 146 146 // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the 147 147 // previous off whenever we add new content to the previous offset whenever we add new content to the list. 148 - if (isWeb && isAtTop.value && hasScrolled) { 148 + if (isWeb && isAtTop.get() && hasScrolled) { 149 149 flatListRef.current?.scrollToOffset({ 150 150 offset: height - prevContentHeight.current, 151 151 animated: false, ··· 153 153 } 154 154 155 155 // This number _must_ be the height of the MaybeLoader component 156 - if (height > 50 && isAtBottom.value) { 156 + if (height > 50 && isAtBottom.get()) { 157 157 // If the size of the content is changing by more than the height of the screen, then we don't 158 158 // want to scroll further than the start of all the new content. Since we are storing the previous offset, 159 159 // we can just scroll the user to that offset and add a little bit of padding. We'll also show the pill ··· 161 161 if ( 162 162 didBackground.current && 163 163 hasScrolled && 164 - height - prevContentHeight.current > layoutHeight.value - 50 && 164 + height - prevContentHeight.current > layoutHeight.get() - 50 && 165 165 convoState.items.length - prevItemCount.current > 1 166 166 ) { 167 167 flatListRef.current?.scrollToOffset({ ··· 209 209 ) 210 210 211 211 const onStartReached = useCallback(() => { 212 - if (hasScrolled && prevContentHeight.current > layoutHeight.value) { 212 + if (hasScrolled && prevContentHeight.current > layoutHeight.get()) { 213 213 convoState.fetchMessageHistory() 214 214 } 215 215 }, [convoState, hasScrolled, layoutHeight]) ··· 217 217 const onScroll = React.useCallback( 218 218 (e: ReanimatedScrollEvent) => { 219 219 'worklet' 220 - layoutHeight.value = e.layoutMeasurement.height 220 + layoutHeight.set(e.layoutMeasurement.height) 221 221 const bottomOffset = e.contentOffset.y + e.layoutMeasurement.height 222 222 223 223 // Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom 224 224 // when a new message is added, hence the 100 pixel offset 225 - isAtBottom.value = e.contentSize.height - 100 < bottomOffset 226 - isAtTop.value = e.contentOffset.y <= 1 225 + isAtBottom.set(e.contentSize.height - 100 < bottomOffset) 226 + isAtTop.set(e.contentOffset.y <= 1) 227 227 228 228 if ( 229 229 newMessagesPill.show && 230 230 (e.contentOffset.y > newMessagesPill.startContentOffset + 200 || 231 - isAtBottom.value) 231 + isAtBottom.get()) 232 232 ) { 233 233 runOnJS(setNewMessagesPill)({ 234 234 show: false, ··· 256 256 // Immediate updates - like opening the emoji picker - will have a duration of zero. In those cases, we should 257 257 // just update the height here instead of having the `onMove` event do it (that event will not fire!) 258 258 if (e.duration === 0) { 259 - layoutScrollWithoutAnimation.value = true 260 - keyboardHeight.value = e.height 259 + layoutScrollWithoutAnimation.set(true) 260 + keyboardHeight.set(e.height) 261 261 } else { 262 - keyboardIsOpening.value = true 262 + keyboardIsOpening.set(true) 263 263 } 264 264 }, 265 265 onMove: e => { 266 266 'worklet' 267 - keyboardHeight.value = e.height 267 + keyboardHeight.set(e.height) 268 268 if (e.height > bottomOffset) { 269 269 scrollTo(flatListRef, 0, 1e7, false) 270 270 } 271 271 }, 272 272 onEnd: () => { 273 273 'worklet' 274 - keyboardIsOpening.value = false 274 + keyboardIsOpening.set(false) 275 275 }, 276 276 }) 277 277 278 278 const animatedListStyle = useAnimatedStyle(() => ({ 279 279 marginBottom: 280 - keyboardHeight.value > bottomOffset ? keyboardHeight.value : bottomOffset, 280 + keyboardHeight.get() > bottomOffset ? keyboardHeight.get() : bottomOffset, 281 281 })) 282 282 283 283 // -- Message sending ··· 363 363 // -- List layout changes (opening emoji keyboard, etc.) 364 364 const onListLayout = React.useCallback( 365 365 (e: LayoutChangeEvent) => { 366 - layoutHeight.value = e.nativeEvent.layout.height 366 + layoutHeight.set(e.nativeEvent.layout.height) 367 367 368 - if (isWeb || !keyboardIsOpening.value) { 368 + if (isWeb || !keyboardIsOpening.get()) { 369 369 flatListRef.current?.scrollToEnd({ 370 - animated: !layoutScrollWithoutAnimation.value, 370 + animated: !layoutScrollWithoutAnimation.get(), 371 371 }) 372 - layoutScrollWithoutAnimation.value = false 372 + layoutScrollWithoutAnimation.set(false) 373 373 } 374 374 }, 375 375 [
+1 -1
src/screens/Profile/Header/GrowableAvatar.tsx
··· 45 45 const animatedStyle = useAnimatedStyle(() => ({ 46 46 transform: [ 47 47 { 48 - scale: interpolate(scrollY.value, [-150, 0], [1.2, 1], { 48 + scale: interpolate(scrollY.get(), [-150, 0], [1.2, 1], { 49 49 extrapolateRight: Extrapolation.CLAMP, 50 50 }), 51 51 },
+8 -7
src/screens/Profile/Header/GrowableBanner.tsx
··· 66 66 const animatedStyle = useAnimatedStyle(() => ({ 67 67 transform: [ 68 68 { 69 - scale: interpolate(scrollY.value, [-150, 0], [2, 1], { 69 + scale: interpolate(scrollY.get(), [-150, 0], [2, 1], { 70 70 extrapolateRight: Extrapolation.CLAMP, 71 71 }), 72 72 }, ··· 76 76 const animatedBlurViewProps = useAnimatedProps(() => { 77 77 return { 78 78 intensity: interpolate( 79 - scrollY.value, 79 + scrollY.get(), 80 80 [-300, -65, -15], 81 81 [50, 40, 0], 82 82 Extrapolation.CLAMP, ··· 85 85 }) 86 86 87 87 const animatedSpinnerStyle = useAnimatedStyle(() => { 88 + const scrollYValue = scrollY.get() 88 89 return { 89 - display: scrollY.value < 0 ? 'flex' : 'none', 90 + display: scrollYValue < 0 ? 'flex' : 'none', 90 91 opacity: interpolate( 91 - scrollY.value, 92 + scrollYValue, 92 93 [-60, -15], 93 94 [1, 0], 94 95 Extrapolation.CLAMP, 95 96 ), 96 97 transform: [ 97 - {translateY: interpolate(scrollY.value, [-150, 0], [-75, 0])}, 98 + {translateY: interpolate(scrollYValue, [-150, 0], [-75, 0])}, 98 99 {rotate: '90deg'}, 99 100 ], 100 101 } ··· 103 104 const animatedBackButtonStyle = useAnimatedStyle(() => ({ 104 105 transform: [ 105 106 { 106 - translateY: interpolate(scrollY.value, [-150, 60], [-150, 60], { 107 + translateY: interpolate(scrollY.get(), [-150, 60], [-150, 60], { 107 108 extrapolateRight: Extrapolation.CLAMP, 108 109 }), 109 110 }, ··· 168 169 const stickyIsOverscrolled = useStickyToggle(isOverscrolled, 10) 169 170 170 171 useAnimatedReaction( 171 - () => scrollY.value < -5, 172 + () => scrollY.get() < -5, 172 173 (value, prevValue) => { 173 174 if (value !== prevValue) { 174 175 runOnJS(setIsOverscrolled)(value)
+10 -6
src/state/shell/minimal-mode.tsx
··· 44 44 'worklet' 45 45 // Cancel any existing animation 46 46 cancelAnimation(headerMode) 47 - headerMode.value = withSpring(v ? 1 : 0, { 48 - overshootClamping: true, 49 - }) 47 + headerMode.set(() => 48 + withSpring(v ? 1 : 0, { 49 + overshootClamping: true, 50 + }), 51 + ) 50 52 cancelAnimation(footerMode) 51 - footerMode.value = withSpring(v ? 1 : 0, { 52 - overshootClamping: true, 53 - }) 53 + footerMode.set(() => 54 + withSpring(v ? 1 : 0, { 55 + overshootClamping: true, 56 + }), 57 + ) 54 58 }, 55 59 [headerMode, footerMode], 56 60 )
+27 -19
src/view/com/composer/Composer.tsx
··· 1267 1267 const contentHeight = useSharedValue(0) 1268 1268 1269 1269 const hasScrolledToTop = useDerivedValue(() => 1270 - withTiming(contentOffset.value === 0 ? 1 : 0), 1270 + withTiming(contentOffset.get() === 0 ? 1 : 0), 1271 1271 ) 1272 1272 1273 1273 const hasScrolledToBottom = useDerivedValue(() => 1274 1274 withTiming( 1275 - contentHeight.value - contentOffset.value - 5 <= scrollViewHeight.value 1275 + contentHeight.get() - contentOffset.get() - 5 <= scrollViewHeight.get() 1276 1276 ? 1 1277 1277 : 0, 1278 1278 ), ··· 1290 1290 }) => { 1291 1291 'worklet' 1292 1292 if (typeof newContentHeight === 'number') 1293 - contentHeight.value = Math.floor(newContentHeight) 1293 + contentHeight.set(Math.floor(newContentHeight)) 1294 1294 if (typeof newContentOffset === 'number') 1295 - contentOffset.value = Math.floor(newContentOffset) 1295 + contentOffset.set(Math.floor(newContentOffset)) 1296 1296 if (typeof newScrollViewHeight === 'number') 1297 - scrollViewHeight.value = Math.floor(newScrollViewHeight) 1297 + scrollViewHeight.set(Math.floor(newScrollViewHeight)) 1298 1298 }, 1299 1299 [contentHeight, contentOffset, scrollViewHeight], 1300 1300 ) ··· 1310 1310 }, 1311 1311 }) 1312 1312 1313 - const onScrollViewContentSizeChange = useCallback( 1314 - (_width: number, height: number) => { 1315 - if (stickyBottom && height > contentHeight.value) { 1313 + const onScrollViewContentSizeChangeUIThread = useCallback( 1314 + (newContentHeight: number) => { 1315 + 'worklet' 1316 + const oldContentHeight = contentHeight.get() 1317 + let shouldScrollToBottom = false 1318 + if (stickyBottom && newContentHeight > oldContentHeight) { 1316 1319 const isFairlyCloseToBottom = 1317 - contentHeight.value - contentOffset.value - 100 <= 1318 - scrollViewHeight.value 1320 + oldContentHeight - contentOffset.get() - 100 <= scrollViewHeight.get() 1319 1321 if (isFairlyCloseToBottom) { 1320 - runOnUI(() => { 1321 - scrollTo(scrollViewRef, 0, contentHeight.value, true) 1322 - })() 1322 + shouldScrollToBottom = true 1323 1323 } 1324 1324 } 1325 - showHideBottomBorder({ 1326 - newContentHeight: height, 1327 - }) 1325 + showHideBottomBorder({newContentHeight}) 1326 + if (shouldScrollToBottom) { 1327 + scrollTo(scrollViewRef, 0, newContentHeight, true) 1328 + } 1328 1329 }, 1329 1330 [ 1330 1331 showHideBottomBorder, ··· 1336 1337 ], 1337 1338 ) 1338 1339 1340 + const onScrollViewContentSizeChange = useCallback( 1341 + (_width: number, height: number) => { 1342 + runOnUI(onScrollViewContentSizeChangeUIThread)(height) 1343 + }, 1344 + [onScrollViewContentSizeChangeUIThread], 1345 + ) 1346 + 1339 1347 const onScrollViewLayout = useCallback( 1340 1348 (evt: LayoutChangeEvent) => { 1341 1349 showHideBottomBorder({ ··· 1349 1357 return { 1350 1358 borderBottomWidth: StyleSheet.hairlineWidth, 1351 1359 borderColor: interpolateColor( 1352 - hasScrolledToTop.value, 1360 + hasScrolledToTop.get(), 1353 1361 [0, 1], 1354 1362 [t.atoms.border_contrast_medium.borderColor, 'transparent'], 1355 1363 ), ··· 1359 1367 return { 1360 1368 borderTopWidth: StyleSheet.hairlineWidth, 1361 1369 borderColor: interpolateColor( 1362 - hasScrolledToBottom.value, 1370 + hasScrolledToBottom.get(), 1363 1371 [0, 1], 1364 1372 [t.atoms.border_contrast_medium.borderColor, 'transparent'], 1365 1373 ), ··· 1604 1612 1605 1613 const animatedStyle = useAnimatedStyle(() => { 1606 1614 return { 1607 - transform: [{rotateZ: `${rotate.value}deg`}], 1615 + transform: [{rotateZ: `${rotate.get()}deg`}], 1608 1616 } 1609 1617 }) 1610 1618
+1 -1
src/view/com/home/HomeHeaderLayout.web.tsx
··· 93 93 {tabBarAnchor} 94 94 <Animated.View 95 95 onLayout={e => { 96 - headerHeight.value = e.nativeEvent.layout.height 96 + headerHeight.set(e.nativeEvent.layout.height) 97 97 }} 98 98 style={[ 99 99 t.atoms.bg,
+1 -1
src/view/com/home/HomeHeaderLayoutMobile.tsx
··· 43 43 <Animated.View 44 44 style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]} 45 45 onLayout={e => { 46 - headerHeight.value = e.nativeEvent.layout.height 46 + headerHeight.set(e.nativeEvent.layout.height) 47 47 }}> 48 48 <View style={[pal.view, styles.topBar]}> 49 49 <View style={[pal.view, {width: 100}]}>
+41 -40
src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
··· 87 87 // Note: DO NOT move any logic reading animated values outside this function. 88 88 useAnimatedReaction( 89 89 () => { 90 - if (pinchScale.value !== 1) { 90 + if (pinchScale.get() !== 1) { 91 91 // We're currently pinching. 92 92 return true 93 93 } 94 - const [, , committedScale] = readTransform(committedTransform.value) 94 + const [, , committedScale] = readTransform(committedTransform.get()) 95 95 if (committedScale !== 1) { 96 96 // We started from a pinched in state. 97 97 return true ··· 147 147 .onStart(e => { 148 148 'worklet' 149 149 const screenSize = measureSafeArea() 150 - pinchOrigin.value = { 150 + pinchOrigin.set({ 151 151 x: e.focalX - screenSize.width / 2, 152 152 y: e.focalY - screenSize.height / 2, 153 - } 153 + }) 154 154 }) 155 155 .onChange(e => { 156 156 'worklet' ··· 160 160 } 161 161 // Don't let the picture zoom in so close that it gets blurry. 162 162 // Also, like in stock Android apps, don't let the user zoom out further than 1:1. 163 - const [, , committedScale] = readTransform(committedTransform.value) 163 + const [, , committedScale] = readTransform(committedTransform.get()) 164 164 const maxCommittedScale = Math.max( 165 165 MIN_SCREEN_ZOOM, 166 166 (imageDimensions.width / screenSize.width) * MAX_ORIGINAL_IMAGE_ZOOM, ··· 171 171 Math.max(minPinchScale, e.scale), 172 172 maxPinchScale, 173 173 ) 174 - pinchScale.value = nextPinchScale 174 + pinchScale.set(nextPinchScale) 175 175 176 176 // Zooming out close to the corner could push us out of bounds, which we don't want on Android. 177 177 // Calculate where we'll end up so we know how much to translate back to stay in bounds. 178 178 const t = createTransform() 179 - prependPan(t, panTranslation.value) 180 - prependPinch(t, nextPinchScale, pinchOrigin.value, pinchTranslation.value) 181 - prependTransform(t, committedTransform.value) 179 + prependPan(t, panTranslation.get()) 180 + prependPinch(t, nextPinchScale, pinchOrigin.get(), pinchTranslation.get()) 181 + prependTransform(t, committedTransform.get()) 182 182 const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize) 183 183 if (dx !== 0 || dy !== 0) { 184 - pinchTranslation.value = { 185 - x: pinchTranslation.value.x + dx, 186 - y: pinchTranslation.value.y + dy, 187 - } 184 + const pt = pinchTranslation.get() 185 + pinchTranslation.set({ 186 + x: pt.x + dx, 187 + y: pt.y + dy, 188 + }) 188 189 } 189 190 }) 190 191 .onEnd(() => { ··· 193 194 let t = createTransform() 194 195 prependPinch( 195 196 t, 196 - pinchScale.value, 197 - pinchOrigin.value, 198 - pinchTranslation.value, 197 + pinchScale.get(), 198 + pinchOrigin.get(), 199 + pinchTranslation.get(), 199 200 ) 200 - prependTransform(t, committedTransform.value) 201 + prependTransform(t, committedTransform.get()) 201 202 applyRounding(t) 202 - committedTransform.value = t 203 + committedTransform.set(t) 203 204 204 205 // Reset just the pinch. 205 - pinchScale.value = 1 206 - pinchOrigin.value = {x: 0, y: 0} 207 - pinchTranslation.value = {x: 0, y: 0} 206 + pinchScale.set(1) 207 + pinchOrigin.set({x: 0, y: 0}) 208 + pinchTranslation.set({x: 0, y: 0}) 208 209 }) 209 210 210 211 const pan = Gesture.Pan() ··· 223 224 prependPan(t, nextPanTranslation) 224 225 prependPinch( 225 226 t, 226 - pinchScale.value, 227 - pinchOrigin.value, 228 - pinchTranslation.value, 227 + pinchScale.get(), 228 + pinchOrigin.get(), 229 + pinchTranslation.get(), 229 230 ) 230 - prependTransform(t, committedTransform.value) 231 + prependTransform(t, committedTransform.get()) 231 232 232 233 // Prevent panning from going out of bounds. 233 234 const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize) 234 235 nextPanTranslation.x += dx 235 236 nextPanTranslation.y += dy 236 - panTranslation.value = nextPanTranslation 237 + panTranslation.set(nextPanTranslation) 237 238 }) 238 239 .onEnd(() => { 239 240 'worklet' 240 241 // Commit just the pan. 241 242 let t = createTransform() 242 - prependPan(t, panTranslation.value) 243 - prependTransform(t, committedTransform.value) 243 + prependPan(t, panTranslation.get()) 244 + prependTransform(t, committedTransform.get()) 244 245 applyRounding(t) 245 - committedTransform.value = t 246 + committedTransform.set(t) 246 247 247 248 // Reset just the pan. 248 - panTranslation.value = {x: 0, y: 0} 249 + panTranslation.set({x: 0, y: 0}) 249 250 }) 250 251 251 252 const singleTap = Gesture.Tap().onEnd(() => { ··· 261 262 if (!imageDimensions || !imageAspect) { 262 263 return 263 264 } 264 - const [, , committedScale] = readTransform(committedTransform.value) 265 + const [, , committedScale] = readTransform(committedTransform.get()) 265 266 if (committedScale !== 1) { 266 267 // Go back to 1:1 using the identity vector. 267 268 let t = createTransform() 268 - committedTransform.value = withClampedSpring(t) 269 + committedTransform.set(withClampedSpring(t)) 269 270 return 270 271 } 271 272 ··· 299 300 ) 300 301 const finalTransform = createTransform() 301 302 prependPinch(finalTransform, scale, origin, {x: dx, y: dy}) 302 - committedTransform.value = withClampedSpring(finalTransform) 303 + committedTransform.set(withClampedSpring(finalTransform)) 303 304 }) 304 305 305 306 const composedGesture = isScrollViewBeingDragged ··· 313 314 ) 314 315 315 316 const containerStyle = useAnimatedStyle(() => { 316 - const {scaleAndMoveTransform, isHidden} = transforms.value 317 + const {scaleAndMoveTransform, isHidden} = transforms.get() 317 318 // Apply the active adjustments on top of the committed transform before the gestures. 318 319 // This is matrix multiplication, so operations are applied in the reverse order. 319 320 let t = createTransform() 320 - prependPan(t, panTranslation.value) 321 - prependPinch(t, pinchScale.value, pinchOrigin.value, pinchTranslation.value) 322 - prependTransform(t, committedTransform.value) 321 + prependPan(t, panTranslation.get()) 322 + prependPinch(t, pinchScale.get(), pinchOrigin.get(), pinchTranslation.get()) 323 + prependTransform(t, committedTransform.get()) 323 324 const [translateX, translateY, scale] = readTransform(t) 324 325 const manipulationTransform = [ 325 326 {translateX}, ··· 338 339 }) 339 340 340 341 const imageCropStyle = useAnimatedStyle(() => { 341 - const {cropFrameTransform} = transforms.value 342 + const {cropFrameTransform} = transforms.get() 342 343 return { 343 344 flex: 1, 344 345 overflow: 'hidden', ··· 347 348 }) 348 349 349 350 const imageStyle = useAnimatedStyle(() => { 350 - const {cropContentTransform} = transforms.value 351 + const {cropContentTransform} = transforms.get() 351 352 return { 352 353 flex: 1, 353 354 transform: cropContentTransform, ··· 359 360 const [hasLoaded, setHasLoaded] = useState(false) 360 361 useAnimatedReaction( 361 362 () => { 362 - return transforms.value.isResting && !hasLoaded 363 + return transforms.get().isResting && !hasLoaded 363 364 }, 364 365 (show, prevShow) => { 365 366 if (show && !prevShow) {
+4 -4
src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
··· 148 148 ) 149 149 150 150 const containerStyle = useAnimatedStyle(() => { 151 - const {scaleAndMoveTransform, isHidden} = transforms.value 151 + const {scaleAndMoveTransform, isHidden} = transforms.get() 152 152 return { 153 153 flex: 1, 154 154 transform: scaleAndMoveTransform, ··· 158 158 159 159 const imageCropStyle = useAnimatedStyle(() => { 160 160 const screenSize = measureSafeArea() 161 - const {cropFrameTransform} = transforms.value 161 + const {cropFrameTransform} = transforms.get() 162 162 return { 163 163 overflow: 'hidden', 164 164 transform: cropFrameTransform, ··· 171 171 }) 172 172 173 173 const imageStyle = useAnimatedStyle(() => { 174 - const {cropContentTransform} = transforms.value 174 + const {cropContentTransform} = transforms.get() 175 175 return { 176 176 transform: cropContentTransform, 177 177 width: '100%', ··· 184 184 const [hasLoaded, setHasLoaded] = useState(false) 185 185 useAnimatedReaction( 186 186 () => { 187 - return transforms.value.isResting && !hasLoaded 187 + return transforms.get().isResting && !hasLoaded 188 188 }, 189 189 (show, prevShow) => { 190 190 if (show && !prevShow) {
+42 -32
src/view/com/lightbox/ImageViewing/index.tsx
··· 109 109 110 110 // https://github.com/software-mansion/react-native-reanimated/issues/6677 111 111 requestAnimationFrame(() => { 112 - openProgress.value = canAnimate ? withClampedSpring(1, SLOW_SPRING) : 1 112 + openProgress.set(() => 113 + canAnimate ? withClampedSpring(1, SLOW_SPRING) : 1, 114 + ) 113 115 }) 114 116 return () => { 115 117 // https://github.com/software-mansion/react-native-reanimated/issues/6677 116 118 requestAnimationFrame(() => { 117 - openProgress.value = canAnimate ? withClampedSpring(0, SLOW_SPRING) : 0 119 + openProgress.set(() => 120 + canAnimate ? withClampedSpring(0, SLOW_SPRING) : 0, 121 + ) 118 122 }) 119 123 } 120 124 }, [nextLightbox, openProgress]) 121 125 122 126 useAnimatedReaction( 123 - () => openProgress.value === 0, 127 + () => openProgress.get() === 0, 124 128 (isGone, wasGone) => { 125 129 if (isGone && !wasGone) { 126 130 runOnJS(setActiveLightbox)(null) ··· 130 134 131 135 const onFlyAway = React.useCallback(() => { 132 136 'worklet' 133 - openProgress.value = 0 137 + openProgress.set(0) 134 138 runOnJS(onRequestClose)() 135 139 }, [onRequestClose, openProgress]) 136 140 ··· 187 191 const isFlyingAway = useSharedValue(false) 188 192 189 193 const containerStyle = useAnimatedStyle(() => { 190 - if (openProgress.value < 1 || isFlyingAway.value) { 194 + if (openProgress.get() < 1 || isFlyingAway.get()) { 191 195 return {pointerEvents: 'none'} 192 196 } 193 197 return {pointerEvents: 'auto'} ··· 196 200 const backdropStyle = useAnimatedStyle(() => { 197 201 const screenSize = measure(safeAreaRef) 198 202 let opacity = 1 199 - if (openProgress.value < 1) { 200 - opacity = Math.sqrt(openProgress.value) 203 + const openProgressValue = openProgress.get() 204 + if (openProgressValue < 1) { 205 + opacity = Math.sqrt(openProgressValue) 201 206 } else if (screenSize) { 202 207 const dragProgress = Math.min( 203 - Math.abs(dismissSwipeTranslateY.value) / (screenSize.height / 2), 208 + Math.abs(dismissSwipeTranslateY.get()) / (screenSize.height / 2), 204 209 1, 205 210 ) 206 211 opacity -= dragProgress ··· 212 217 }) 213 218 214 219 const animatedHeaderStyle = useAnimatedStyle(() => { 215 - const show = showControls && dismissSwipeTranslateY.value === 0 220 + const show = showControls && dismissSwipeTranslateY.get() === 0 216 221 return { 217 222 pointerEvents: show ? 'box-none' : 'none', 218 223 opacity: withClampedSpring( 219 - show && openProgress.value === 1 ? 1 : 0, 224 + show && openProgress.get() === 1 ? 1 : 0, 220 225 FAST_SPRING, 221 226 ), 222 227 transform: [ ··· 227 232 } 228 233 }) 229 234 const animatedFooterStyle = useAnimatedStyle(() => { 230 - const show = showControls && dismissSwipeTranslateY.value === 0 235 + const show = showControls && dismissSwipeTranslateY.get() === 0 231 236 return { 232 237 flexGrow: 1, 233 238 pointerEvents: show ? 'box-none' : 'none', 234 239 opacity: withClampedSpring( 235 - show && openProgress.value === 1 ? 1 : 0, 240 + show && openProgress.get() === 1 ? 1 : 0, 236 241 FAST_SPRING, 237 242 ), 238 243 transform: [ ··· 259 264 const screenSize = measure(safeAreaRef) 260 265 return ( 261 266 !screenSize || 262 - Math.abs(dismissSwipeTranslateY.value) > screenSize.height 267 + Math.abs(dismissSwipeTranslateY.get()) > screenSize.height 263 268 ) 264 269 }, 265 270 (isOut, wasOut) => { ··· 397 402 const transforms = useDerivedValue(() => { 398 403 'worklet' 399 404 const safeArea = measureSafeArea() 405 + const openProgressValue = openProgress.get() 400 406 const dismissTranslateY = 401 - isActive && openProgress.value === 1 ? dismissSwipeTranslateY.value : 0 407 + isActive && openProgressValue === 1 ? dismissSwipeTranslateY.get() : 0 402 408 403 - if (openProgress.value === 0 && isFlyingAway.value) { 409 + if (openProgressValue === 0 && isFlyingAway.get()) { 404 410 return { 405 411 isHidden: true, 406 412 isResting: false, ··· 410 416 } 411 417 } 412 418 413 - if (isActive && thumbRect && imageAspect && openProgress.value < 1) { 419 + if (isActive && thumbRect && imageAspect && openProgressValue < 1) { 414 420 return interpolateTransform( 415 - openProgress.value, 421 + openProgressValue, 416 422 thumbRect, 417 423 safeArea, 418 424 imageAspect, ··· 434 440 .maxPointers(1) 435 441 .onUpdate(e => { 436 442 'worklet' 437 - if (openProgress.value !== 1 || isFlyingAway.value) { 443 + if (openProgress.get() !== 1 || isFlyingAway.get()) { 438 444 return 439 445 } 440 - dismissSwipeTranslateY.value = e.translationY 446 + dismissSwipeTranslateY.set(e.translationY) 441 447 }) 442 448 .onEnd(e => { 443 449 'worklet' 444 - if (openProgress.value !== 1 || isFlyingAway.value) { 450 + if (openProgress.get() !== 1 || isFlyingAway.get()) { 445 451 return 446 452 } 447 453 if (Math.abs(e.velocityY) > 200) { 448 - isFlyingAway.value = true 449 - if (dismissSwipeTranslateY.value === 0) { 454 + isFlyingAway.set(true) 455 + if (dismissSwipeTranslateY.get() === 0) { 450 456 // HACK: If the initial value is 0, withDecay() animation doesn't start. 451 457 // This is a bug in Reanimated, but for now we'll work around it like this. 452 - dismissSwipeTranslateY.value = 1 458 + dismissSwipeTranslateY.set(1) 453 459 } 454 - dismissSwipeTranslateY.value = withDecay({ 455 - velocity: e.velocityY, 456 - velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), // Speed up if it's too slow. 457 - deceleration: 1, // Danger! This relies on the reaction below stopping it. 458 - }) 460 + dismissSwipeTranslateY.set(() => 461 + withDecay({ 462 + velocity: e.velocityY, 463 + velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), // Speed up if it's too slow. 464 + deceleration: 1, // Danger! This relies on the reaction below stopping it. 465 + }), 466 + ) 459 467 } else { 460 - dismissSwipeTranslateY.value = withSpring(0, { 461 - stiffness: 700, 462 - damping: 50, 463 - }) 468 + dismissSwipeTranslateY.set(() => 469 + withSpring(0, { 470 + stiffness: 700, 471 + damping: 50, 472 + }), 473 + ) 464 474 } 465 475 }) 466 476
+6 -6
src/view/com/pager/PagerWithHeader.tsx
··· 131 131 const lastForcedScrollY = useSharedValue(0) 132 132 const adjustScrollForOtherPages = () => { 133 133 'worklet' 134 - const currentScrollY = scrollY.value 134 + const currentScrollY = scrollY.get() 135 135 const forcedScrollY = Math.min(currentScrollY, headerOnlyHeight) 136 - if (lastForcedScrollY.value !== forcedScrollY) { 137 - lastForcedScrollY.value = forcedScrollY 138 - const refs = scrollRefs.value 136 + if (lastForcedScrollY.get() !== forcedScrollY) { 137 + lastForcedScrollY.set(forcedScrollY) 138 + const refs = scrollRefs.get() 139 139 for (let i = 0; i < refs.length; i++) { 140 140 const scollRef = refs[i] 141 141 if (i !== currentPage && scollRef != null) { ··· 167 167 const isPossiblyInvalid = 168 168 headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight 169 169 if (!isPossiblyInvalid) { 170 - scrollY.value = nextScrollY 170 + scrollY.set(nextScrollY) 171 171 runOnJS(queueThrottledOnScroll)() 172 172 } 173 173 }, ··· 246 246 allowHeaderOverScroll?: boolean 247 247 }): React.ReactNode => { 248 248 const headerTransform = useAnimatedStyle(() => { 249 - const translateY = Math.min(scrollY.value, headerOnlyHeight) * -1 249 + const translateY = Math.min(scrollY.get(), headerOnlyHeight) * -1 250 250 return { 251 251 transform: [ 252 252 {
+1 -1
src/view/com/util/BottomSheetCustomBackdrop.tsx
··· 18 18 // animated variables 19 19 const opacity = useAnimatedStyle(() => ({ 20 20 opacity: interpolate( 21 - animatedIndex.value, // current snap index 21 + animatedIndex.get(), // current snap index 22 22 [-1, 0], // input range 23 23 [0, 0.5], // output range 24 24 Extrapolation.CLAMP,
+2 -2
src/view/com/util/List.tsx
··· 79 79 onScrollFromContext?.(e, ctx) 80 80 81 81 const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT 82 - if (isScrolledDown.value !== didScrollDown) { 83 - isScrolledDown.value = didScrollDown 82 + if (isScrolledDown.get() !== didScrollDown) { 83 + isScrolledDown.set(didScrollDown) 84 84 if (onScrolledDownChange != null) { 85 85 runOnJS(handleScrolledDownChange)(didScrollDown) 86 86 }
+27 -24
src/view/com/util/MainScrollProvider.tsx
··· 44 44 (v: boolean) => { 45 45 'worklet' 46 46 cancelAnimation(headerMode) 47 - headerMode.value = v ? V1.value : V0.value 47 + headerMode.set(v ? V1.get() : V0.get()) 48 48 }, 49 49 [headerMode], 50 50 ) ··· 52 52 useEffect(() => { 53 53 if (isWeb) { 54 54 return listenToForcedWindowScroll(() => { 55 - startDragOffset.value = null 56 - startMode.value = null 57 - didJustRestoreScroll.value = true 55 + startDragOffset.set(null) 56 + startMode.set(null) 57 + didJustRestoreScroll.set(true) 58 58 }) 59 59 } 60 60 }) ··· 63 63 (e: NativeScrollEvent) => { 64 64 'worklet' 65 65 if (isNative) { 66 - if (startDragOffset.value === null) { 66 + const startDragOffsetValue = startDragOffset.get() 67 + if (startDragOffsetValue === null) { 67 68 return 68 69 } 69 - const didScrollDown = e.contentOffset.y > startDragOffset.value 70 - startDragOffset.value = null 71 - startMode.value = null 72 - if (e.contentOffset.y < headerHeight.value) { 70 + const didScrollDown = e.contentOffset.y > startDragOffsetValue 71 + startDragOffset.set(null) 72 + startMode.set(null) 73 + if (e.contentOffset.y < headerHeight.get()) { 73 74 // If we're close to the top, show the shell. 74 75 setMode(false) 75 76 } else if (didScrollDown) { ··· 77 78 setMode(true) 78 79 } else { 79 80 // Snap to whichever state is the closest. 80 - setMode(Math.round(headerMode.value) === 1) 81 + setMode(Math.round(headerMode.get()) === 1) 81 82 } 82 83 } 83 84 }, ··· 88 89 (e: NativeScrollEvent) => { 89 90 'worklet' 90 91 if (isNative) { 91 - startDragOffset.value = e.contentOffset.y 92 - startMode.value = headerMode.value 92 + startDragOffset.set(e.contentOffset.y) 93 + startMode.set(headerMode.get()) 93 94 } 94 95 }, 95 96 [headerMode, startDragOffset, startMode], ··· 123 124 (e: NativeScrollEvent) => { 124 125 'worklet' 125 126 if (isNative) { 126 - if (startDragOffset.value === null || startMode.value === null) { 127 + const startDragOffsetValue = startDragOffset.get() 128 + const startModeValue = startMode.get() 129 + if (startDragOffsetValue === null || startModeValue === null) { 127 130 if ( 128 - headerMode.value !== 0 && 129 - e.contentOffset.y < headerHeight.value 131 + headerMode.get() !== 0 && 132 + e.contentOffset.y < headerHeight.get() 130 133 ) { 131 134 // If we're close enough to the top, always show the shell. 132 135 // Even if we're not dragging. ··· 137 140 138 141 // The "mode" value is always between 0 and 1. 139 142 // Figure out how much to move it based on the current dragged distance. 140 - const dy = e.contentOffset.y - startDragOffset.value 143 + const dy = e.contentOffset.y - startDragOffsetValue 141 144 const dProgress = interpolate( 142 145 dy, 143 - [-headerHeight.value, headerHeight.value], 146 + [-headerHeight.get(), headerHeight.get()], 144 147 [-1, 1], 145 148 ) 146 - const newValue = clamp(startMode.value + dProgress, 0, 1) 147 - if (newValue !== headerMode.value) { 149 + const newValue = clamp(startModeValue + dProgress, 0, 1) 150 + if (newValue !== headerMode.get()) { 148 151 // Manually adjust the value. This won't be (and shouldn't be) animated. 149 152 // Cancel any any existing animation 150 153 cancelAnimation(headerMode) 151 - headerMode.value = newValue 154 + headerMode.set(newValue) 152 155 } 153 156 } else { 154 - if (didJustRestoreScroll.value) { 155 - didJustRestoreScroll.value = false 157 + if (didJustRestoreScroll.get()) { 158 + didJustRestoreScroll.set(false) 156 159 // Don't hide/show navbar based on scroll restoratoin. 157 160 return 158 161 } 159 162 // On the web, we don't try to follow the drag because we don't know when it ends. 160 163 // Instead, show/hide immediately based on whether we're scrolling up or down. 161 - const dy = e.contentOffset.y - (startDragOffset.value ?? 0) 162 - startDragOffset.value = e.contentOffset.y 164 + const dy = e.contentOffset.y - (startDragOffset.get() ?? 0) 165 + startDragOffset.set(e.contentOffset.y) 163 166 164 167 if (dy < 0 || e.contentOffset.y < WEB_HIDE_SHELL_THRESHOLD) { 165 168 setMode(false)
+1 -1
src/view/shell/bottom-bar/BottomBar.tsx
··· 134 134 footerMinimalShellTransform, 135 135 ]} 136 136 onLayout={e => { 137 - footerHeight.value = e.nativeEvent.layout.height 137 + footerHeight.set(e.nativeEvent.layout.height) 138 138 }}> 139 139 {hasSession ? ( 140 140 <>