Fix stuck bottom bar on Home and use the Reanimated 4 spring curve
The header and bottom bar were being handed the same `withSpring()` animation object. Animation objects carry their own state, so the second shared value to receive it could stop partway, leaving the bar stuck half hidden after a slow scroll on Home. Each shared value now gets its own animation. Shell show/hide springs now share one config based on Reanimated 4's default critically damped curve, with the perceptual duration shortened from 550ms to 400ms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWQciPDiipBhqiW922Nivs
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import {type WithSpringConfig} from 'react-native-reanimated'
|
||||
|
||||
/**
|
||||
* Spring used for showing and hiding shell UI (header, bottom bar, and things
|
||||
* attached to them). This is Reanimated 4's default critically damped curve,
|
||||
* whose perceptual duration is 550ms, sped up a little.
|
||||
*/
|
||||
export const SHELL_SPRING_CONFIG = {
|
||||
duration: 400,
|
||||
dampingRatio: 1,
|
||||
} as const satisfies WithSpringConfig
|
||||
@@ -1,13 +1,14 @@
|
||||
import {useCallback, useState} from 'react'
|
||||
import {
|
||||
clamp,
|
||||
Reanimated3DefaultSpringConfig,
|
||||
type SharedValue,
|
||||
useDerivedValue,
|
||||
withSpring,
|
||||
} from 'react-native-reanimated'
|
||||
import {scheduleOnRN} from 'react-native-worklets'
|
||||
|
||||
import {SHELL_SPRING_CONFIG} from '#/lib/custom-animations/springs'
|
||||
|
||||
type Contribution = {
|
||||
id: number
|
||||
value: SharedValue<number>
|
||||
@@ -52,15 +53,11 @@ export function useContributionRegistry(base?: SharedValue<number>) {
|
||||
setContributions(prev => [...prev, {id, value}])
|
||||
return () => {
|
||||
value.set(
|
||||
withSpring(
|
||||
0,
|
||||
{...Reanimated3DefaultSpringConfig, overshootClamping: true},
|
||||
finished => {
|
||||
if (finished) {
|
||||
scheduleOnRN(remove, id)
|
||||
}
|
||||
},
|
||||
),
|
||||
withSpring(0, SHELL_SPRING_CONFIG, finished => {
|
||||
if (finished) {
|
||||
scheduleOnRN(remove, id)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {useContext, useEffect} from 'react'
|
||||
import {
|
||||
Reanimated3DefaultSpringConfig,
|
||||
type SharedValue,
|
||||
useDerivedValue,
|
||||
useSharedValue,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
type ParamListBase,
|
||||
} from '@react-navigation/native'
|
||||
|
||||
import {SHELL_SPRING_CONFIG} from '#/lib/custom-animations/springs'
|
||||
import {ScreenPresenceContext} from './context'
|
||||
|
||||
export {type ScreenPresence, useScreenPresence} from './context'
|
||||
@@ -117,10 +117,10 @@ function useAncestorsFocused(navigation: NavigationProp<ParamListBase>) {
|
||||
if (ancestors.length === 0) return
|
||||
const update = () => {
|
||||
focused.set(
|
||||
withSpring(areAncestorsFocused(navigation) ? 1 : 0, {
|
||||
...Reanimated3DefaultSpringConfig,
|
||||
overshootClamping: true,
|
||||
}),
|
||||
withSpring(
|
||||
areAncestorsFocused(navigation) ? 1 : 0,
|
||||
SHELL_SPRING_CONFIG,
|
||||
),
|
||||
)
|
||||
}
|
||||
update()
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import {useCallback} from 'react'
|
||||
import {
|
||||
Reanimated3DefaultSpringConfig,
|
||||
useSharedValue,
|
||||
withSpring,
|
||||
} from 'react-native-reanimated'
|
||||
import {useSharedValue, withSpring} from 'react-native-reanimated'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {SHELL_SPRING_CONFIG} from '#/lib/custom-animations/springs'
|
||||
import {ScreenPresenceContext} from './context'
|
||||
|
||||
export {type ScreenPresence, useScreenPresence} from './context'
|
||||
@@ -35,13 +32,8 @@ export function ScreenPresenceProvider({
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const spring = (to: number) =>
|
||||
withSpring(to, {
|
||||
...Reanimated3DefaultSpringConfig,
|
||||
overshootClamping: true,
|
||||
})
|
||||
presence.set(spring(1))
|
||||
return () => presence.set(spring(0))
|
||||
presence.set(withSpring(1, SHELL_SPRING_CONFIG))
|
||||
return () => presence.set(withSpring(0, SHELL_SPRING_CONFIG))
|
||||
}, [presence]),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {createContext, useContext, useEffect, useMemo} from 'react'
|
||||
import {
|
||||
type DerivedValue,
|
||||
Reanimated3DefaultSpringConfig,
|
||||
type SharedValue,
|
||||
useAnimatedReaction,
|
||||
useSharedValue,
|
||||
withSpring,
|
||||
} from 'react-native-reanimated'
|
||||
|
||||
import {SHELL_SPRING_CONFIG} from '#/lib/custom-animations/springs'
|
||||
import {useContributionRegistry} from '#/lib/hooks/useContributionRegistry'
|
||||
import {useScreenPresence} from '#/lib/hooks/useScreenPresence'
|
||||
|
||||
@@ -84,12 +84,7 @@ export function useEnableMinimalShellMode({enabled} = {enabled: true}) {
|
||||
useEffect(() => {
|
||||
if (!enabled) return
|
||||
const unregister = register(contribution)
|
||||
contribution.set(
|
||||
withSpring(1, {
|
||||
...Reanimated3DefaultSpringConfig,
|
||||
overshootClamping: true,
|
||||
}),
|
||||
)
|
||||
contribution.set(withSpring(1, SHELL_SPRING_CONFIG))
|
||||
return unregister
|
||||
}, [enabled, register, contribution])
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import {type NativeScrollEvent} from 'react-native'
|
||||
import {
|
||||
clamp,
|
||||
interpolate,
|
||||
Reanimated3DefaultSpringConfig,
|
||||
type SharedValue,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
|
||||
import {SHELL_SPRING_CONFIG} from '#/lib/custom-animations/springs'
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {useMinimalShellScrollMode} from '#/state/shell/minimal-mode'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
@@ -106,12 +106,9 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
const setMode = useCallback(
|
||||
(v: boolean) => {
|
||||
'worklet'
|
||||
const target = withSpring(v ? 1 : 0, {
|
||||
...Reanimated3DefaultSpringConfig,
|
||||
overshootClamping: true,
|
||||
})
|
||||
headerMode.set(target)
|
||||
footerScrollMode.set(target)
|
||||
// animation objects are stateful, so each shared value needs its own
|
||||
headerMode.set(withSpring(v ? 1 : 0, SHELL_SPRING_CONFIG))
|
||||
footerScrollMode.set(withSpring(v ? 1 : 0, SHELL_SPRING_CONFIG))
|
||||
},
|
||||
[headerMode, footerScrollMode],
|
||||
)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import {useCallback, useEffect, useLayoutEffect, useMemo, useRef} from 'react'
|
||||
import {ActivityIndicator, AppState, StyleSheet} from 'react-native'
|
||||
import {
|
||||
Reanimated3DefaultSpringConfig,
|
||||
withSpring,
|
||||
} from 'react-native-reanimated'
|
||||
import {withSpring} from 'react-native-reanimated'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
@@ -12,6 +9,7 @@ import {
|
||||
PROD_DEFAULT_FEED,
|
||||
TIMELINE_SAVED_FEED,
|
||||
} from '#/lib/constants'
|
||||
import {SHELL_SPRING_CONFIG} from '#/lib/custom-animations/springs'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {useOTAUpdates} from '#/lib/hooks/useOTAUpdates'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
@@ -160,12 +158,9 @@ function HomeScreenReady({
|
||||
const footerScrollMode = useMinimalShellScrollMode()
|
||||
const showShell = useCallback(() => {
|
||||
'worklet'
|
||||
const shown = withSpring(0, {
|
||||
...Reanimated3DefaultSpringConfig,
|
||||
overshootClamping: true,
|
||||
})
|
||||
headerMode.set(shown)
|
||||
footerScrollMode.set(shown)
|
||||
// animation objects are stateful, so each shared value needs its own
|
||||
headerMode.set(withSpring(0, SHELL_SPRING_CONFIG))
|
||||
footerScrollMode.set(withSpring(0, SHELL_SPRING_CONFIG))
|
||||
}, [headerMode, footerScrollMode])
|
||||
|
||||
useFocusEffect(
|
||||
|
||||
Reference in New Issue
Block a user