From b147f7ae8a5c8c3a1ec9981132b4c6447034963f Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 11 Jan 2024 18:44:56 +0000 Subject: [PATCH] Toggle minimal shell on any scroll for web (#2499) --- src/view/com/util/MainScrollProvider.tsx | 89 +++++++++++++----------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/src/view/com/util/MainScrollProvider.tsx b/src/view/com/util/MainScrollProvider.tsx index 31a4ef0c..3ac28d31 100644 --- a/src/view/com/util/MainScrollProvider.tsx +++ b/src/view/com/util/MainScrollProvider.tsx @@ -3,9 +3,11 @@ import {ScrollProvider} from '#/lib/ScrollContext' import {NativeScrollEvent} from 'react-native' import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell' import {useShellLayout} from '#/state/shell/shell-layout' -import {isWeb} from 'platform/detection' +import {isNative} from 'platform/detection' import {useSharedValue, interpolate} from 'react-native-reanimated' +const WEB_HIDE_SHELL_THRESHOLD = 200 + function clamp(num: number, min: number, max: number) { 'worklet' return Math.min(Math.max(num, min), max) @@ -21,8 +23,10 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { const onBeginDrag = useCallback( (e: NativeScrollEvent) => { 'worklet' - startDragOffset.value = e.contentOffset.y - startMode.value = mode.value + if (isNative) { + startDragOffset.value = e.contentOffset.y + startMode.value = mode.value + } }, [mode, startDragOffset, startMode], ) @@ -30,14 +34,16 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { const onEndDrag = useCallback( (e: NativeScrollEvent) => { 'worklet' - startDragOffset.value = null - startMode.value = null - if (e.contentOffset.y < headerHeight.value / 2) { - // If we're close to the top, show the shell. - setMode(false) - } else { - // Snap to whichever state is the closest. - setMode(Math.round(mode.value) === 1) + if (isNative) { + startDragOffset.value = null + startMode.value = null + if (e.contentOffset.y < headerHeight.value / 2) { + // If we're close to the top, show the shell. + setMode(false) + } else { + // Snap to whichever state is the closest. + setMode(Math.round(mode.value) === 1) + } } }, [startDragOffset, startMode, setMode, mode, headerHeight], @@ -46,41 +52,40 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { const onScroll = useCallback( (e: NativeScrollEvent) => { 'worklet' - if (startDragOffset.value === null || startMode.value === null) { - if (mode.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) + if (isNative) { + if (startDragOffset.value === null || startMode.value === null) { + if (mode.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) + } return } - if (isWeb) { - // On the web, there is no concept of "starting" the drag. - // When we get the first scroll event, we consider that the start. - startDragOffset.value = e.contentOffset.y - startMode.value = mode.value - } - return - } - // The "mode" value is always between 0 and 1. - // Figure out how much to move it based on the current dragged distance. - const dy = e.contentOffset.y - startDragOffset.value - const dProgress = interpolate( - dy, - [-headerHeight.value, headerHeight.value], - [-1, 1], - ) - const newValue = clamp(startMode.value + dProgress, 0, 1) - if (newValue !== mode.value) { - // Manually adjust the value. This won't be (and shouldn't be) animated. - mode.value = newValue - } - if (isWeb) { - // On the web, there is no concept of "starting" the drag, - // so we don't have any specific anchor point to calculate the distance. - // Instead, update it continuosly along the way and diff with the last event. + // The "mode" value is always between 0 and 1. + // Figure out how much to move it based on the current dragged distance. + const dy = e.contentOffset.y - startDragOffset.value + const dProgress = interpolate( + dy, + [-headerHeight.value, headerHeight.value], + [-1, 1], + ) + const newValue = clamp(startMode.value + dProgress, 0, 1) + if (newValue !== mode.value) { + // Manually adjust the value. This won't be (and shouldn't be) animated. + mode.value = newValue + } + } else { + // On the web, we don't try to follow the drag because we don't know when it ends. + // Instead, show/hide immediately based on whether we're scrolling up or down. + const dy = e.contentOffset.y - (startDragOffset.value ?? 0) startDragOffset.value = e.contentOffset.y - startMode.value = mode.value + + if (dy < 0 || e.contentOffset.y < WEB_HIDE_SHELL_THRESHOLD) { + setMode(false) + } else if (dy > 0) { + setMode(true) + } } }, [headerHeight, mode, setMode, startDragOffset, startMode],