diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/hooks/useMinimalShellTransform.ts | 45 | ||||
-rw-r--r-- | src/state/shell/minimal-mode.tsx | 43 | ||||
-rw-r--r-- | src/view/com/util/MainScrollProvider.tsx | 53 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 6 |
4 files changed, 95 insertions, 52 deletions
diff --git a/src/lib/hooks/useMinimalShellTransform.ts b/src/lib/hooks/useMinimalShellTransform.ts index 17fe058e9..678776755 100644 --- a/src/lib/hooks/useMinimalShellTransform.ts +++ b/src/lib/hooks/useMinimalShellTransform.ts @@ -2,21 +2,24 @@ import {interpolate, useAnimatedStyle} from 'react-native-reanimated' import {useMinimalShellMode} from '#/state/shell/minimal-mode' import {useShellLayout} from '#/state/shell/shell-layout' -import {useGate} from '../statsig/statsig' // Keep these separated so that we only pay for useAnimatedStyle that gets used. export function useMinimalShellHeaderTransform() { - const mode = useMinimalShellMode() + const {headerMode} = useMinimalShellMode() const {headerHeight} = useShellLayout() const headerTransform = useAnimatedStyle(() => { return { - pointerEvents: mode.value === 0 ? 'auto' : 'none', - opacity: Math.pow(1 - mode.value, 2), + pointerEvents: headerMode.value === 0 ? 'auto' : 'none', + opacity: Math.pow(1 - headerMode.value, 2), transform: [ { - translateY: interpolate(mode.value, [0, 1], [0, -headerHeight.value]), + translateY: interpolate( + headerMode.value, + [0, 1], + [0, -headerHeight.value], + ), }, ], } @@ -26,21 +29,20 @@ export function useMinimalShellHeaderTransform() { } export function useMinimalShellFooterTransform() { - const mode = useMinimalShellMode() + const {footerMode} = useMinimalShellMode() const {footerHeight} = useShellLayout() - const gate = useGate() - const isFixedBottomBar = gate('fixed_bottom_bar') const footerTransform = useAnimatedStyle(() => { - if (isFixedBottomBar) { - return {} - } return { - pointerEvents: mode.value === 0 ? 'auto' : 'none', - opacity: Math.pow(1 - mode.value, 2), + pointerEvents: footerMode.value === 0 ? 'auto' : 'none', + opacity: Math.pow(1 - footerMode.value, 2), transform: [ { - translateY: interpolate(mode.value, [0, 1], [0, footerHeight.value]), + translateY: interpolate( + footerMode.value, + [0, 1], + [0, footerHeight.value], + ), }, ], } @@ -50,24 +52,13 @@ export function useMinimalShellFooterTransform() { } export function useMinimalShellFabTransform() { - const mode = useMinimalShellMode() - const gate = useGate() - const isFixedBottomBar = gate('fixed_bottom_bar') + const {footerMode} = useMinimalShellMode() const fabTransform = useAnimatedStyle(() => { - if (isFixedBottomBar) { - return { - transform: [ - { - translateY: -44, - }, - ], - } - } return { transform: [ { - translateY: interpolate(mode.value, [0, 1], [-44, 0]), + translateY: interpolate(footerMode.value, [0, 1], [-44, 0]), }, ], } diff --git a/src/state/shell/minimal-mode.tsx b/src/state/shell/minimal-mode.tsx index 69ce13062..9230339dd 100644 --- a/src/state/shell/minimal-mode.tsx +++ b/src/state/shell/minimal-mode.tsx @@ -6,32 +6,55 @@ import { withSpring, } from 'react-native-reanimated' -type StateContext = SharedValue<number> +type StateContext = { + headerMode: SharedValue<number> + footerMode: SharedValue<number> +} type SetContext = (v: boolean) => void const stateContext = React.createContext<StateContext>({ - value: 0, - addListener() {}, - removeListener() {}, - modify() {}, + headerMode: { + value: 0, + addListener() {}, + removeListener() {}, + modify() {}, + }, + footerMode: { + value: 0, + addListener() {}, + removeListener() {}, + modify() {}, + }, }) const setContext = React.createContext<SetContext>((_: boolean) => {}) export function Provider({children}: React.PropsWithChildren<{}>) { - const mode = useSharedValue(0) + const headerMode = useSharedValue(0) + const footerMode = useSharedValue(0) const setMode = React.useCallback( (v: boolean) => { 'worklet' // Cancel any existing animation - cancelAnimation(mode) - mode.value = withSpring(v ? 1 : 0, { + cancelAnimation(headerMode) + headerMode.value = withSpring(v ? 1 : 0, { + overshootClamping: true, + }) + cancelAnimation(footerMode) + footerMode.value = withSpring(v ? 1 : 0, { overshootClamping: true, }) }, - [mode], + [headerMode, footerMode], + ) + const value = React.useMemo( + () => ({ + headerMode, + footerMode, + }), + [headerMode, footerMode], ) return ( - <stateContext.Provider value={mode}> + <stateContext.Provider value={value}> <setContext.Provider value={setMode}>{children}</setContext.Provider> </stateContext.Provider> ) diff --git a/src/view/com/util/MainScrollProvider.tsx b/src/view/com/util/MainScrollProvider.tsx index b602da432..3163d8544 100644 --- a/src/view/com/util/MainScrollProvider.tsx +++ b/src/view/com/util/MainScrollProvider.tsx @@ -4,11 +4,13 @@ import { cancelAnimation, interpolate, useSharedValue, + withSpring, } from 'react-native-reanimated' import EventEmitter from 'eventemitter3' import {ScrollProvider} from '#/lib/ScrollContext' -import {useMinimalShellMode, useSetMinimalShellMode} from '#/state/shell' +import {useGate} from '#/lib/statsig/statsig' +import {useMinimalShellMode} from '#/state/shell' import {useShellLayout} from '#/state/shell/shell-layout' import {isNative, isWeb} from 'platform/detection' @@ -21,11 +23,29 @@ function clamp(num: number, min: number, max: number) { export function MainScrollProvider({children}: {children: React.ReactNode}) { const {headerHeight} = useShellLayout() - const mode = useMinimalShellMode() - const setMode = useSetMinimalShellMode() + const {headerMode, footerMode} = useMinimalShellMode() const startDragOffset = useSharedValue<number | null>(null) const startMode = useSharedValue<number | null>(null) const didJustRestoreScroll = useSharedValue<boolean>(false) + const gate = useGate() + const isFixedBottomBar = gate('fixed_bottom_bar') + + const setMode = React.useCallback( + (v: boolean) => { + 'worklet' + cancelAnimation(headerMode) + headerMode.value = withSpring(v ? 1 : 0, { + overshootClamping: true, + }) + if (!isFixedBottomBar) { + cancelAnimation(footerMode) + footerMode.value = withSpring(v ? 1 : 0, { + overshootClamping: true, + }) + } + }, + [headerMode, footerMode, isFixedBottomBar], + ) useEffect(() => { if (isWeb) { @@ -55,11 +75,11 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { setMode(true) } else { // Snap to whichever state is the closest. - setMode(Math.round(mode.value) === 1) + setMode(Math.round(headerMode.value) === 1) } } }, - [startDragOffset, startMode, setMode, mode, headerHeight], + [startDragOffset, startMode, setMode, headerMode, headerHeight], ) const onBeginDrag = useCallback( @@ -67,10 +87,10 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { 'worklet' if (isNative) { startDragOffset.value = e.contentOffset.y - startMode.value = mode.value + startMode.value = headerMode.value } }, - [mode, startDragOffset, startMode], + [headerMode, startDragOffset, startMode], ) const onEndDrag = useCallback( @@ -102,7 +122,10 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { 'worklet' if (isNative) { if (startDragOffset.value === null || startMode.value === null) { - if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) { + if ( + headerMode.value !== 0 && + e.contentOffset.y < headerHeight.value + ) { // If we're close enough to the top, always show the shell. // Even if we're not dragging. setMode(false) @@ -119,11 +142,15 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { [-1, 1], ) const newValue = clamp(startMode.value + dProgress, 0, 1) - if (newValue !== mode.value) { + if (newValue !== headerMode.value) { // Manually adjust the value. This won't be (and shouldn't be) animated. // Cancel any any existing animation - cancelAnimation(mode) - mode.value = newValue + cancelAnimation(headerMode) + headerMode.value = newValue + if (!isFixedBottomBar) { + cancelAnimation(footerMode) + footerMode.value = newValue + } } } else { if (didJustRestoreScroll.value) { @@ -145,11 +172,13 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { }, [ headerHeight, - mode, + headerMode, + footerMode, setMode, startDragOffset, startMode, didJustRestoreScroll, + isFixedBottomBar, ], ) diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 9a47007c4..af424428d 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -167,7 +167,7 @@ function HomeScreenReady({ }), ) - const mode = useMinimalShellMode() + const {footerMode} = useMinimalShellMode() const {isMobile} = useWebMediaQueries() useFocusEffect( React.useCallback(() => { @@ -177,7 +177,7 @@ function HomeScreenReady({ } const listener = AppState.addEventListener('change', nextAppState => { if (nextAppState === 'active') { - if (isMobile && mode.value === 1) { + if (isMobile && footerMode.value === 1) { // Reveal the bottom bar so you don't miss notifications or messages. // TODO: Experiment with only doing it when unread > 0. setMinimalShellMode(false) @@ -187,7 +187,7 @@ function HomeScreenReady({ return () => { listener.remove() } - }, [setMinimalShellMode, mode, isMobile, gate]), + }, [setMinimalShellMode, footerMode, isMobile, gate]), ) const onPageSelected = React.useCallback( |