Account for momentum when hiding minimal shell (#3740)

* Add optional momentum events to scroll context

* If there is a velocity, don't snap until momentum end

* Don't show bar on scroll down

* Rm onMomentumBegin
zio/stable
dan 2024-04-28 17:48:20 +01:00 committed by GitHub
parent 3b4848ba59
commit 1dd3d6657c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 15 deletions

View File

@ -5,6 +5,7 @@ const ScrollContext = createContext<ScrollHandlers<any>>({
onBeginDrag: undefined,
onEndDrag: undefined,
onScroll: undefined,
onMomentumEnd: undefined,
})
export function useScrollHandlers(): ScrollHandlers<any> {
@ -20,14 +21,16 @@ export function ScrollProvider({
onBeginDrag,
onEndDrag,
onScroll,
onMomentumEnd,
}: ProviderProps) {
const handlers = useMemo(
() => ({
onBeginDrag,
onEndDrag,
onScroll,
onMomentumEnd,
}),
[onBeginDrag, onEndDrag, onScroll],
[onBeginDrag, onEndDrag, onScroll, onMomentumEnd],
)
return (
<ScrollContext.Provider value={handlers}>{children}</ScrollContext.Provider>

View File

@ -123,6 +123,9 @@ export function ProfileLabelsSectionInner({
onScroll(e, ctx) {
contextScrollHandlers.onScroll?.(e, ctx)
},
onMomentumEnd(e, ctx) {
contextScrollHandlers.onMomentumEnd?.(e, ctx)
},
})
const {labelValues} = labelerInfo.policies

View File

@ -64,6 +64,11 @@ function ListImpl<ItemT>(
}
}
},
// Note: adding onMomentumBegin here makes simulator scroll
// lag on Android. So either don't add it, or figure out why.
onMomentumEnd(e, ctx) {
contextScrollHandlers.onMomentumEnd?.(e, ctx)
},
})
let refreshControl

View File

@ -1,11 +1,12 @@
import React, {useCallback, useEffect} from 'react'
import EventEmitter from 'eventemitter3'
import {ScrollProvider} from '#/lib/ScrollContext'
import {NativeScrollEvent} from 'react-native'
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
import {interpolate, useSharedValue} from 'react-native-reanimated'
import EventEmitter from 'eventemitter3'
import {ScrollProvider} from '#/lib/ScrollContext'
import {useMinimalShellMode, useSetMinimalShellMode} from '#/state/shell'
import {useShellLayout} from '#/state/shell/shell-layout'
import {isNative, isWeb} from 'platform/detection'
import {useSharedValue, interpolate} from 'react-native-reanimated'
const WEB_HIDE_SHELL_THRESHOLD = 200
@ -32,6 +33,31 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
}
})
const snapToClosestState = useCallback(
(e: NativeScrollEvent) => {
'worklet'
if (isNative) {
if (startDragOffset.value === null) {
return
}
const didScrollDown = e.contentOffset.y > startDragOffset.value
startDragOffset.value = null
startMode.value = null
if (e.contentOffset.y < headerHeight.value) {
// If we're close to the top, show the shell.
setMode(false)
} else if (didScrollDown) {
// Showing the bar again on scroll down feels annoying, so don't.
setMode(true)
} else {
// Snap to whichever state is the closest.
setMode(Math.round(mode.value) === 1)
}
}
},
[startDragOffset, startMode, setMode, mode, headerHeight],
)
const onBeginDrag = useCallback(
(e: NativeScrollEvent) => {
'worklet'
@ -47,18 +73,24 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
(e: NativeScrollEvent) => {
'worklet'
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)
if (e.velocity && e.velocity.y !== 0) {
// If we detect a velocity, wait for onMomentumEnd to snap.
return
}
snapToClosestState(e)
}
},
[startDragOffset, startMode, setMode, mode, headerHeight],
[snapToClosestState],
)
const onMomentumEnd = useCallback(
(e: NativeScrollEvent) => {
'worklet'
if (isNative) {
snapToClosestState(e)
}
},
[snapToClosestState],
)
const onScroll = useCallback(
@ -119,7 +151,8 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
<ScrollProvider
onBeginDrag={onBeginDrag}
onEndDrag={onEndDrag}
onScroll={onScroll}>
onScroll={onScroll}
onMomentumEnd={onMomentumEnd}>
{children}
</ScrollProvider>
)