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 onMomentumBeginzio/stable
parent
3b4848ba59
commit
1dd3d6657c
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue