diff --git a/package.json b/package.json index dde81171ea..a6166c6653 100644 --- a/package.json +++ b/package.json @@ -376,8 +376,8 @@ "reanimated": { "staticFeatureFlags": { "DISABLE_COMMIT_PAUSING_MECHANISM": true, - "ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS": true, - "IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS": true, + "ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS": false, + "IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS": false, "USE_SYNCHRONIZABLE_FOR_MUTABLES": true, "USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS": true, "FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS": true diff --git a/src/screens/Profile/Header/GrowableAvatar.tsx b/src/screens/Profile/Header/GrowableAvatar.tsx index ac66046d63..a69399e4c6 100644 --- a/src/screens/Profile/Header/GrowableAvatar.tsx +++ b/src/screens/Profile/Header/GrowableAvatar.tsx @@ -1,6 +1,5 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' import Animated, { - Extrapolation, interpolate, type SharedValue, useAnimatedStyle, @@ -24,30 +23,28 @@ export function GrowableAvatar({ return {children} } - const {scrollY} = pagerContext + const {clampedScrollY} = pagerContext return ( - + {children} ) } function GrowableAvatarInner({ - scrollY, + clampedScrollY, children, style, }: { - scrollY: SharedValue + clampedScrollY: SharedValue children: React.ReactNode style?: StyleProp }) { const animatedStyle = useAnimatedStyle(() => ({ transform: [ { - scale: interpolate(scrollY.get(), [-150, 0], [1.2, 1], { - extrapolateRight: Extrapolation.CLAMP, - }), + scale: interpolate(clampedScrollY.get(), [-150, 0], [1.2, 1]), }, ], })) diff --git a/src/screens/Profile/Header/GrowableBanner.tsx b/src/screens/Profile/Header/GrowableBanner.tsx index 3cd7b97fc8..da134857b4 100644 --- a/src/screens/Profile/Header/GrowableBanner.tsx +++ b/src/screens/Profile/Header/GrowableBanner.tsx @@ -44,34 +44,37 @@ export function GrowableBanner({ ) } - const {scrollY} = pagerContext + const {clampedScrollY} = pagerContext return ( - + {children} ) } function GrowableBannerInner({ - scrollY, + clampedScrollY, backButton, children, }: { - scrollY: SharedValue + clampedScrollY: SharedValue backButton?: React.ReactNode children: React.ReactNode }) { const {top: topInset} = useSafeAreaInsets() const isFetching = useIsProfileFetching() - const animateSpinner = useShouldAnimateSpinner({isFetching, scrollY}) + const animateSpinner = useShouldAnimateSpinner({ + isFetching, + clampedScrollY, + }) const animatedStyle = useAnimatedStyle(() => ({ transform: [ { - scale: interpolate(scrollY.get(), [-150, 0], [2, 1], { - extrapolateRight: Extrapolation.CLAMP, - }), + scale: interpolate(clampedScrollY.get(), [-150, 0], [2, 1]), }, ], })) @@ -79,7 +82,7 @@ function GrowableBannerInner({ const animatedBlurViewProps = useAnimatedProps(() => { return { intensity: interpolate( - scrollY.get(), + clampedScrollY.get(), [-300, -65, -15], [50, 40, 0], Extrapolation.CLAMP, @@ -88,7 +91,7 @@ function GrowableBannerInner({ }) const animatedSpinnerStyle = useAnimatedStyle(() => { - const scrollYValue = scrollY.get() + const scrollYValue = clampedScrollY.get() return { display: scrollYValue < 0 ? 'flex' : 'none', opacity: interpolate( @@ -98,11 +101,7 @@ function GrowableBannerInner({ Extrapolation.CLAMP, ), transform: [ - { - translateY: interpolate(scrollYValue, [-150, 0], [-75, 0], { - extrapolateRight: Extrapolation.CLAMP, - }), - }, + {translateY: interpolate(scrollYValue, [-150, 0], [-75, 0])}, {rotate: '90deg'}, ], } @@ -111,9 +110,7 @@ function GrowableBannerInner({ const animatedBackButtonStyle = useAnimatedStyle(() => ({ transform: [ { - translateY: interpolate(scrollY.get(), [-150, 10], [-150, 10], { - extrapolateRight: Extrapolation.CLAMP, - }), + translateY: Math.max(-150, Math.min(10, clampedScrollY.get())), }, ], })) @@ -172,10 +169,10 @@ function useIsProfileFetching() { function useShouldAnimateSpinner({ isFetching, - scrollY, + clampedScrollY, }: { isFetching: boolean - scrollY: SharedValue + clampedScrollY: SharedValue }) { const [isOverscrolled, setIsOverscrolled] = useState(false) // HACK: it reports a scroll pos of 0 for a tick when fetching finishes @@ -183,13 +180,13 @@ function useShouldAnimateSpinner({ const stickyIsOverscrolled = useStickyToggle(isOverscrolled, 10) useAnimatedReaction( - () => scrollY.get() < -5, + () => clampedScrollY.get() < -5, (value, prevValue) => { if (value !== prevValue) { runOnJS(setIsOverscrolled)(value) } }, - [scrollY], + [clampedScrollY], ) const [isAnimating, setIsAnimating] = useState(isFetching) diff --git a/src/screens/Profile/Header/StatusBarShadow.tsx b/src/screens/Profile/Header/StatusBarShadow.tsx index 9fefc378fb..336e6e5216 100644 --- a/src/screens/Profile/Header/StatusBarShadow.tsx +++ b/src/screens/Profile/Header/StatusBarShadow.tsx @@ -14,8 +14,8 @@ export function StatusBarShadow() { const pagerContext = usePagerHeaderContext() if (isIOS && pagerContext) { - const {scrollY} = pagerContext - return + const {clampedScrollY} = pagerContext + return } return ( @@ -30,14 +30,18 @@ export function StatusBarShadow() { ) } -function StatusBarShadowInnner({scrollY}: {scrollY: SharedValue}) { +function StatusBarShadowInnner({ + clampedScrollY, +}: { + clampedScrollY: SharedValue +}) { const {top: topInset} = useSafeAreaInsets() const animatedStyle = useAnimatedStyle(() => { return { transform: [ { - translateY: Math.min(0, scrollY.get()), + translateY: Math.min(0, clampedScrollY.get()), }, ], } diff --git a/src/view/com/pager/PagerHeaderContext.tsx b/src/view/com/pager/PagerHeaderContext.tsx index b4ebcdcf94..0cf6a37df8 100644 --- a/src/view/com/pager/PagerHeaderContext.tsx +++ b/src/view/com/pager/PagerHeaderContext.tsx @@ -1,10 +1,11 @@ -import React, {useContext} from 'react' -import {type SharedValue} from 'react-native-reanimated' +import {createContext, useContext, useMemo} from 'react' +import {type SharedValue, useDerivedValue} from 'react-native-reanimated' import {isNative} from '#/platform/detection' -export const PagerHeaderContext = React.createContext<{ +export const PagerHeaderContext = createContext<{ scrollY: SharedValue + clampedScrollY: SharedValue headerHeight: number } | null>(null) PagerHeaderContext.displayName = 'PagerHeaderContext' @@ -24,9 +25,16 @@ export function PagerHeaderProvider({ headerHeight: number children: React.ReactNode }) { - const value = React.useMemo( - () => ({scrollY, headerHeight}), - [scrollY, headerHeight], + // bit of a hackfix - most of the usage of scrollY here is for the pull-to-refresh + // behaviour in the header, and after scrolling it's not needed anymore. + // to improve performance, we create a derived value that clamps up-front, + // therefore removing the need for the downstream animated styles to run on every frame + // when they won't actually be used -sfn + const clampedScrollY = useDerivedValue(() => Math.min(100, scrollY.get())) + + const value = useMemo( + () => ({scrollY, clampedScrollY, headerHeight}), + [scrollY, headerHeight, clampedScrollY], ) return (