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 (