From 2ea31cb36193bdab0ddb7f7267f327f5c158bbc7 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Mon, 29 Jun 2026 20:07:02 +0200 Subject: [PATCH] Bump Reanimated from 3.19.1 to 4.3.1 --- babel.config.js | 2 +- package.json | 3 +- patches/react-native-reanimated@3.19.1.patch | 390 ------------------- pnpm-workspace.yaml | 4 +- src/components/ContextMenu/index.tsx | 2 - src/components/DraggableList/index.tsx | 4 +- src/components/Layout/index.tsx | 108 +++-- src/components/Lightbox/pager/ImagePager.tsx | 2 - src/components/Lightbox/types.ts | 3 +- src/components/ProgressGuide/Toast.tsx | 10 +- src/components/dms/MessageItem.tsx | 3 +- src/view/com/composer/Composer.tsx | 5 +- 12 files changed, 69 insertions(+), 467 deletions(-) delete mode 100644 patches/react-native-reanimated@3.19.1.patch diff --git a/babel.config.js b/babel.config.js index 24bdde26f2..1a11b2cb1f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -29,7 +29,7 @@ module.exports = function (api) { }, }, ], - 'react-native-reanimated/plugin', // NOTE: this plugin MUST be last + 'react-native-worklets/plugin', // NOTE: this plugin MUST be last ], env: { production: { diff --git a/package.json b/package.json index b0aed98b52..b5e6bed0c8 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "react-native-pager-view": "6.8.0", "react-native-progress": "bluesky-social/react-native-progress", "react-native-qrcode-styled": "^0.3.3", - "react-native-reanimated": "3.19.1", + "react-native-reanimated": "~4.3.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "4.24.0", "react-native-scroll-forwarder": "link:./modules/react-native-scroll-forwarder", @@ -239,6 +239,7 @@ "react-native-web": "^0.21.0", "react-native-web-webview": "^1.0.2", "react-native-webview": "^13.15.0", + "react-native-worklets": "0.8.3", "react-remove-scroll-bar": "^2.3.8", "react-responsive": "^10.0.1", "react-textarea-autosize": "^8.5.3", diff --git a/patches/react-native-reanimated@3.19.1.patch b/patches/react-native-reanimated@3.19.1.patch deleted file mode 100644 index 8cc124d14b..0000000000 --- a/patches/react-native-reanimated@3.19.1.patch +++ /dev/null @@ -1,390 +0,0 @@ -diff --git a/lib/module/component/PerformanceMonitor.js b/lib/module/component/PerformanceMonitor.js -index 9c98d6cc395419f50969753a4e4c7962b7be588f..3686a97280ac540efa0814ac4dea40358860a76f 100644 ---- a/lib/module/component/PerformanceMonitor.js -+++ b/lib/module/component/PerformanceMonitor.js -@@ -1,125 +1,5 @@ - 'use strict'; - --import React, { useEffect, useRef } from 'react'; --import { StyleSheet, TextInput, View } from 'react-native'; --import { addWhitelistedNativeProps } from "../ConfigHelper.js"; --import { createAnimatedComponent } from "../createAnimatedComponent/index.js"; --import { useAnimatedProps, useFrameCallback, useSharedValue } from "../hook/index.js"; --function createCircularDoublesBuffer(size) { -- 'worklet'; -- -- return { -- next: 0, -- buffer: new Float32Array(size), -- size, -- count: 0, -- push(value) { -- const oldValue = this.buffer[this.next]; -- const oldCount = this.count; -- this.buffer[this.next] = value; -- this.next = (this.next + 1) % this.size; -- this.count = Math.min(this.size, this.count + 1); -- return oldCount === this.size ? oldValue : null; -- }, -- front() { -- const notEmpty = this.count > 0; -- if (notEmpty) { -- const current = this.next - 1; -- const index = current < 0 ? this.size - 1 : current; -- return this.buffer[index]; -- } -- return null; -- }, -- back() { -- const notEmpty = this.count > 0; -- return notEmpty ? this.buffer[this.next] : null; -- } -- }; --} --const DEFAULT_BUFFER_SIZE = 20; --addWhitelistedNativeProps({ -- text: true --}); --const AnimatedTextInput = createAnimatedComponent(TextInput); --function loopAnimationFrame(fn) { -- let lastTime = 0; -- function loop() { -- requestAnimationFrame(time => { -- if (lastTime > 0) { -- fn(lastTime, time); -- } -- lastTime = time; -- requestAnimationFrame(loop); -- }); -- } -- loop(); --} --function getFps(renderTimeInMs) { -- 'worklet'; -- -- return 1000 / renderTimeInMs; --} --function completeBufferRoutine(buffer, timestamp) { -- 'worklet'; -- -- timestamp = Math.round(timestamp); -- const droppedTimestamp = buffer.push(timestamp) ?? timestamp; -- const measuredRangeDuration = timestamp - droppedTimestamp; -- return getFps(measuredRangeDuration / buffer.count); --} --function JsPerformance({ -- smoothingFrames --}) { -- const jsFps = useSharedValue(null); -- const totalRenderTime = useSharedValue(0); -- const circularBuffer = useRef(createCircularDoublesBuffer(smoothingFrames)); -- useEffect(() => { -- loopAnimationFrame((_, timestamp) => { -- timestamp = Math.round(timestamp); -- const currentFps = completeBufferRoutine(circularBuffer.current, timestamp); -- -- // JS fps have to be measured every 2nd frame, -- // thus 2x multiplication has to occur here -- jsFps.value = (currentFps * 2).toFixed(0); -- }); -- }, [jsFps, totalRenderTime]); -- const animatedProps = useAnimatedProps(() => { -- const text = 'JS: ' + (jsFps.value ?? 'N/A') + ' '; -- return { -- text, -- defaultValue: text -- }; -- }); -- return -- -- ; --} --function UiPerformance({ -- smoothingFrames --}) { -- const uiFps = useSharedValue(null); -- const circularBuffer = useSharedValue(null); -- useFrameCallback(({ -- timestamp -- }) => { -- if (circularBuffer.value === null) { -- circularBuffer.value = createCircularDoublesBuffer(smoothingFrames); -- } -- timestamp = Math.round(timestamp); -- const currentFps = completeBufferRoutine(circularBuffer.value, timestamp); -- uiFps.value = currentFps.toFixed(0); -- }); -- const animatedProps = useAnimatedProps(() => { -- const text = 'UI: ' + (uiFps.value ?? 'N/A') + ' '; -- return { -- text, -- defaultValue: text -- }; -- }); -- return -- -- ; --} - /** - * A component that lets you measure fps values on JS and UI threads on both the - * Paper and Fabric architectures. -@@ -127,38 +7,7 @@ function UiPerformance({ - * @param smoothingFrames - Determines amount of saved frames which will be used - * for fps value smoothing. - */ --export function PerformanceMonitor({ -- smoothingFrames = DEFAULT_BUFFER_SIZE --}) { -- return -- -- -- ; -+export function PerformanceMonitor() { -+ return null; - } --const styles = StyleSheet.create({ -- monitor: { -- flexDirection: 'row', -- position: 'absolute', -- backgroundColor: '#0006', -- zIndex: 1000 -- }, -- header: { -- fontSize: 14, -- color: '#ffff', -- paddingHorizontal: 5 -- }, -- text: { -- fontSize: 13, -- fontVariant: ['tabular-nums'], -- color: '#ffff', -- fontFamily: 'monospace', -- paddingHorizontal: 3 -- }, -- container: { -- alignItems: 'center', -- justifyContent: 'center', -- flexDirection: 'row', -- flexWrap: 'wrap' -- } --}); - //# sourceMappingURL=PerformanceMonitor.js.map -\ No newline at end of file -diff --git a/src/component/PerformanceMonitor.tsx b/src/component/PerformanceMonitor.tsx -index ff8fc8a947a0aeb959e21ec061882c3d190a2ce0..34dde79727765623df80dbcdab5016de6fd5d82c 100644 ---- a/src/component/PerformanceMonitor.tsx -+++ b/src/component/PerformanceMonitor.tsx -@@ -1,170 +1,5 @@ - 'use strict'; - --import React, { useEffect, useRef } from 'react'; --import { StyleSheet, TextInput, View } from 'react-native'; -- --import { addWhitelistedNativeProps } from '../ConfigHelper'; --import { createAnimatedComponent } from '../createAnimatedComponent'; --import type { FrameInfo } from '../frameCallback'; --import { useAnimatedProps, useFrameCallback, useSharedValue } from '../hook'; -- --type CircularBuffer = ReturnType; --function createCircularDoublesBuffer(size: number) { -- 'worklet'; -- -- return { -- next: 0 as number, -- buffer: new Float32Array(size), -- size, -- count: 0 as number, -- -- push(value: number): number | null { -- const oldValue = this.buffer[this.next]; -- const oldCount = this.count; -- this.buffer[this.next] = value; -- -- this.next = (this.next + 1) % this.size; -- this.count = Math.min(this.size, this.count + 1); -- return oldCount === this.size ? oldValue : null; -- }, -- -- front(): number | null { -- const notEmpty = this.count > 0; -- if (notEmpty) { -- const current = this.next - 1; -- const index = current < 0 ? this.size - 1 : current; -- return this.buffer[index]; -- } -- return null; -- }, -- -- back(): number | null { -- const notEmpty = this.count > 0; -- return notEmpty ? this.buffer[this.next] : null; -- }, -- }; --} -- --const DEFAULT_BUFFER_SIZE = 20; --addWhitelistedNativeProps({ text: true }); --const AnimatedTextInput = createAnimatedComponent(TextInput); -- --function loopAnimationFrame(fn: (lastTime: number, time: number) => void) { -- let lastTime = 0; -- -- function loop() { -- requestAnimationFrame((time) => { -- if (lastTime > 0) { -- fn(lastTime, time); -- } -- lastTime = time; -- requestAnimationFrame(loop); -- }); -- } -- -- loop(); --} -- --function getFps(renderTimeInMs: number): number { -- 'worklet'; -- return 1000 / renderTimeInMs; --} -- --function completeBufferRoutine( -- buffer: CircularBuffer, -- timestamp: number --): number { -- 'worklet'; -- timestamp = Math.round(timestamp); -- -- const droppedTimestamp = buffer.push(timestamp) ?? timestamp; -- -- const measuredRangeDuration = timestamp - droppedTimestamp; -- -- return getFps(measuredRangeDuration / buffer.count); --} -- --function JsPerformance({ smoothingFrames }: { smoothingFrames: number }) { -- const jsFps = useSharedValue(null); -- const totalRenderTime = useSharedValue(0); -- const circularBuffer = useRef( -- createCircularDoublesBuffer(smoothingFrames) -- ); -- -- useEffect(() => { -- loopAnimationFrame((_, timestamp) => { -- timestamp = Math.round(timestamp); -- -- const currentFps = completeBufferRoutine( -- circularBuffer.current, -- timestamp -- ); -- -- // JS fps have to be measured every 2nd frame, -- // thus 2x multiplication has to occur here -- jsFps.value = (currentFps * 2).toFixed(0); -- }); -- }, [jsFps, totalRenderTime]); -- -- const animatedProps = useAnimatedProps(() => { -- const text = 'JS: ' + (jsFps.value ?? 'N/A') + ' '; -- return { text, defaultValue: text }; -- }); -- -- return ( -- -- -- -- ); --} -- --function UiPerformance({ smoothingFrames }: { smoothingFrames: number }) { -- const uiFps = useSharedValue(null); -- const circularBuffer = useSharedValue(null); -- -- useFrameCallback(({ timestamp }: FrameInfo) => { -- if (circularBuffer.value === null) { -- circularBuffer.value = createCircularDoublesBuffer(smoothingFrames); -- } -- -- timestamp = Math.round(timestamp); -- -- const currentFps = completeBufferRoutine(circularBuffer.value, timestamp); -- -- uiFps.value = currentFps.toFixed(0); -- }); -- -- const animatedProps = useAnimatedProps(() => { -- const text = 'UI: ' + (uiFps.value ?? 'N/A') + ' '; -- return { text, defaultValue: text }; -- }); -- -- return ( -- -- -- -- ); --} -- --export type PerformanceMonitorProps = { -- /** -- * Sets amount of previous frames used for smoothing at highest expectedFps. -- * -- * Automatically scales down at lower frame rates. -- * -- * Affects jumpiness of the FPS measurements value. -- */ -- smoothingFrames?: number; --}; -- - /** - * A component that lets you measure fps values on JS and UI threads on both the - * Paper and Fabric architectures. -@@ -172,40 +7,6 @@ export type PerformanceMonitorProps = { - * @param smoothingFrames - Determines amount of saved frames which will be used - * for fps value smoothing. - */ --export function PerformanceMonitor({ -- smoothingFrames = DEFAULT_BUFFER_SIZE, --}: PerformanceMonitorProps) { -- return ( -- -- -- -- -- ); -+export function PerformanceMonitor() { -+ return null; - } -- --const styles = StyleSheet.create({ -- monitor: { -- flexDirection: 'row', -- position: 'absolute', -- backgroundColor: '#0006', -- zIndex: 1000, -- }, -- header: { -- fontSize: 14, -- color: '#ffff', -- paddingHorizontal: 5, -- }, -- text: { -- fontSize: 13, -- fontVariant: ['tabular-nums'], -- color: '#ffff', -- fontFamily: 'monospace', -- paddingHorizontal: 3, -- }, -- container: { -- alignItems: 'center', -- justifyContent: 'center', -- flexDirection: 'row', -- flexWrap: 'wrap', -- }, --}); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 908b4b3314..19c937d66f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,7 +9,8 @@ overrides: '@expo/image-utils': '0.8.12' '@types/estree': '1.0.6' 'react-native-compressor': '1.13.0' - 'react-native-reanimated': '3.19.1' + 'react-native-reanimated': '4.3.1' + 'react-native-worklets': '0.8.3' 'psl': '1.9.0' '@types/psl': '1.1.1' 'react-native-screens': '4.24.0' @@ -35,7 +36,6 @@ patchedDependencies: 'react-native-drawer-layout@4.2.3': patches/react-native-drawer-layout@4.2.3.patch 'react-native-keyboard-controller@1.21.8': patches/react-native-keyboard-controller@1.21.8.patch 'react-native-pager-view@6.8.0': patches/react-native-pager-view@6.8.0.patch - 'react-native-reanimated@3.19.1': patches/react-native-reanimated@3.19.1.patch 'react-native-svg@15.12.1': patches/react-native-svg@15.12.1.patch 'react-native-view-shot@4.0.3': patches/react-native-view-shot@4.0.3.patch 'react-native@0.81.5': patches/react-native@0.81.5.patch diff --git a/src/components/ContextMenu/index.tsx b/src/components/ContextMenu/index.tsx index b1eb873216..fdb4fa194d 100644 --- a/src/components/ContextMenu/index.tsx +++ b/src/components/ContextMenu/index.tsx @@ -89,14 +89,12 @@ const SPRING_IN: WithSpringConfig = { mass: 0.75, damping: 300, stiffness: 1200, - restDisplacementThreshold: 0.01, } const SPRING_OUT: WithSpringConfig = { mass: IS_IOS ? 1.25 : 0.75, damping: 150, stiffness: 1000, - restDisplacementThreshold: 0.01, } /** diff --git a/src/components/DraggableList/index.tsx b/src/components/DraggableList/index.tsx index 08676bd172..78b893b698 100644 --- a/src/components/DraggableList/index.tsx +++ b/src/components/DraggableList/index.tsx @@ -237,8 +237,8 @@ function SortableItem({ itemKey: string itemCount: number itemHeight: number - state: Animated.SharedValue - dragY: Animated.SharedValue + state: SharedValue + dragY: SharedValue scrollCompensation: SharedValue isGestureActive: SharedValue measureDone: SharedValue diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index c98dd7aed8..2564325935 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -1,4 +1,4 @@ -import {forwardRef, memo, useContext, useMemo} from 'react' +import {memo, useContext, useMemo} from 'react' import { type StyleProp, View, @@ -6,6 +6,7 @@ import { type ViewStyle, } from 'react-native' import Animated, { + type AnimatedRef, type AnimatedScrollViewProps, useAnimatedStyle, } from 'react-native-reanimated' @@ -73,67 +74,64 @@ export type ContentProps = AnimatedScrollViewProps & { style?: StyleProp contentContainerStyle?: StyleProp ignoreTabletLayoutOffset?: boolean + ref?: AnimatedRef } /** * Default scroll view for simple pages */ -export const Content = memo( - forwardRef(function Content( - { - children, - style, - contentContainerStyle, - ignoreTabletLayoutOffset, - ...props - }, - ref, - ) { - const t = useTheme() - const {footerHeight} = useShellLayout() - const {isWithinSplitView} = useIsWithinSplitView() +export const Content = memo(function Content({ + children, + style, + contentContainerStyle, + ignoreTabletLayoutOffset, + ref, + ...props +}: ContentProps) { + const t = useTheme() + const {footerHeight} = useShellLayout() + const {isWithinSplitView} = useIsWithinSplitView() - // note - if we ever make the footer transparent in any way, - // we'll need to change this to use contentInsets/scrollIndicatorInsets - // on iOS and contentContainerStyle padding on Android -sfn - const animatedStyle = useAnimatedStyle(() => { - return { - marginBottom: footerHeight.get(), - } - }) + // note - if we ever make the footer transparent in any way, + // we'll need to change this to use contentInsets/scrollIndicatorInsets + // on iOS and contentContainerStyle padding on Android -sfn + const animatedStyle = useAnimatedStyle(() => { + return { + marginBottom: footerHeight.get(), + } + }) - return ( - - {IS_WEB ? ( -
- {/* @ts-expect-error web only -esb */} - {children} -
- ) : ( - children - )} -
- ) - }), -) + return ( + + {IS_WEB ? ( +
+ {/* @ts-expect-error web only -esb */} + {children} +
+ ) : ( + children + )} +
+ ) +}) /** * Utility component to center content within the screen diff --git a/src/components/Lightbox/pager/ImagePager.tsx b/src/components/Lightbox/pager/ImagePager.tsx index c960490e91..94a39022e7 100644 --- a/src/components/Lightbox/pager/ImagePager.tsx +++ b/src/components/Lightbox/pager/ImagePager.tsx @@ -60,13 +60,11 @@ const SLOW_SPRING: WithSpringConfig = { mass: IS_IOS ? 1.25 : 0.75, damping: 300, stiffness: 800, - restDisplacementThreshold: 0.001, } const FAST_SPRING: WithSpringConfig = { mass: IS_IOS ? 1.25 : 0.75, damping: 150, stiffness: 900, - restDisplacementThreshold: 0.001, } function canAnimate(lightbox: Lightbox): boolean { diff --git a/src/components/Lightbox/types.ts b/src/components/Lightbox/types.ts index 19789fdc62..db68917452 100644 --- a/src/components/Lightbox/types.ts +++ b/src/components/Lightbox/types.ts @@ -6,7 +6,6 @@ * */ -import {type Component} from 'react' import {type TransformsStyle} from 'react-native' import { type AnimatedRef, @@ -29,7 +28,7 @@ export type ImageSource = { thumbUri: string thumbDimensions: Dimensions | null thumbRect: MeasuredDimensions | null - thumbRef?: AnimatedRef | null + thumbRef?: AnimatedRef | null thumbBorderRadius?: number alt?: string type: 'image' | 'circle-avi' | 'rect-avi' diff --git a/src/components/ProgressGuide/Toast.tsx b/src/components/ProgressGuide/Toast.tsx index e1e36e4502..5f509f89a1 100644 --- a/src/components/ProgressGuide/Toast.tsx +++ b/src/components/ProgressGuide/Toast.tsx @@ -119,7 +119,8 @@ export const ProgressGuideToast = forwardRef< left = right = (winDim.width - 380) / 2 } return { - position: IS_WEB ? 'fixed' : 'absolute', + // position: fixed is web only + position: (IS_WEB ? 'fixed' : 'absolute') as 'absolute', top: 0, left, right, @@ -134,12 +135,7 @@ export const ProgressGuideToast = forwardRef< return ( isOpen && ( - + - style?: StyleProp + style?: AnimatedStyle }) { const {t: l} = useLingui() const t = useTheme() diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index a09aaab8c1..ce6b17b8cb 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -25,6 +25,7 @@ import {KeyboardAvoidingView} from 'react-native-keyboard-controller' import ProgressCircle from 'react-native-progress/Circle' import Animated, { type AnimatedRef, + type AnimatedStyle, Easing, FadeIn, FadeOut, @@ -1794,7 +1795,7 @@ function ComposerTopBar({ isEditingDraft: boolean canSaveDraft: boolean textLength: number - topBarAnimatedStyle: StyleProp + topBarAnimatedStyle: AnimatedStyle children?: React.ReactNode }) { const t = useTheme() @@ -2029,7 +2030,7 @@ function ComposerPills({ thread: ThreadDraft post: PostDraft dispatch: (action: ComposerAction) => void - bottomBarAnimatedStyle: StyleProp + bottomBarAnimatedStyle: AnimatedStyle }) { const t = useTheme() const media = post.embed.media