Sync the bottom bar to screen transitions and scroll
## Summary - Hook the bottom bar up to react-native-screens' transition progress. Every `Layout.Screen` now exposes a `presence` shared value (via `useScreenPresence`) that follows the native push/pop/swipe frame by frame, so screens with `minimalShell` hide and reveal the bar in sync with the transition instead of after it. Fixes the delayed tab bar when leaving a conversation. - Replace the minimal-shell and hide-border refcounts with a small registry that sums 0..1 contributions on the UI thread. The bottom bar border now fades with the screen rather than toggling. - Bring back the scroll-linked bottom bar on Home (removed in #5203). The header and footer share the same drag-linked value, and the bar is revealed on feed change, blur, and app foreground so badges aren't missed. Web included. ## Test plan - [ ] Swipe back from a conversation: bar tracks the swipe, including a cancelled swipe - [ ] Back button from a conversation: bar animates with the pop - [ ] Push a profile over a conversation and pop back - [ ] Post thread: bottom bar border fades in/out with the screen - [ ] Home: scroll down hides header and bar together, scroll up shows them - [ ] Home: scrolled down, swipe into a convo and back - no jump - [ ] Home: bar reappears on feed switch, on leaving Home, and on foregrounding the app - [ ] Deep link / notification into a conversation from another tab - [ ] Mobile web: bar hides/shows on scroll and on conversation screen - [ ] Rapid swipe-back and swipe-back while scrolling (software-mansion/react-native-reanimated#9402, software-mansion/react-native-screens#2659) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWQciPDiipBhqiW922Nivs
This commit is contained in:
@@ -13,6 +13,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {useMinimalShellScrollMode} from '#/state/shell/minimal-mode'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
|
||||
|
||||
@@ -95,6 +96,7 @@ export function useHomeHeaderTransform() {
|
||||
export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
const {headerHeight} = useShellLayout()
|
||||
const headerMode = useHomeHeaderMode()
|
||||
const footerScrollMode = useMinimalShellScrollMode()
|
||||
const {top: topInset} = useSafeAreaInsets()
|
||||
const headerPinnedHeight = IS_LIQUID_GLASS ? topInset : 0
|
||||
const startDragOffset = useSharedValue<number | null>(null)
|
||||
@@ -104,14 +106,14 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
const setMode = useCallback(
|
||||
(v: boolean) => {
|
||||
'worklet'
|
||||
headerMode.set(
|
||||
withSpring(v ? 1 : 0, {
|
||||
...Reanimated3DefaultSpringConfig,
|
||||
overshootClamping: true,
|
||||
}),
|
||||
)
|
||||
const target = withSpring(v ? 1 : 0, {
|
||||
...Reanimated3DefaultSpringConfig,
|
||||
overshootClamping: true,
|
||||
})
|
||||
headerMode.set(target)
|
||||
footerScrollMode.set(target)
|
||||
},
|
||||
[headerMode],
|
||||
[headerMode, footerScrollMode],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -216,6 +218,7 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
if (newValue !== headerMode.get()) {
|
||||
// Manually adjust the value. This won't be (and shouldn't be) animated.
|
||||
headerMode.set(newValue)
|
||||
footerScrollMode.set(newValue)
|
||||
}
|
||||
} else {
|
||||
if (didJustRestoreScroll.get()) {
|
||||
@@ -239,6 +242,7 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
headerHeight,
|
||||
headerPinnedHeight,
|
||||
headerMode,
|
||||
footerScrollMode,
|
||||
setMode,
|
||||
startDragOffset,
|
||||
startMode,
|
||||
|
||||
Reference in New Issue
Block a user