Push useAnimatedScrollHandler down everywhere to work around bugs (#1866)

* Move useOnMainScroll handlers to leaves

* Force Feed to always take handlers

* Pass handlers from the pager
This commit is contained in:
dan 2023-11-10 19:00:46 +00:00 committed by GitHub
parent e0e5bc8fd8
commit 65def37165
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 95 additions and 38 deletions

View file

@ -1 +1,15 @@
// Be warned. This Hook is very buggy unless used in a very constrained way.
// To use it safely:
//
// - DO NOT pass its return value as a prop to any user-defined component.
// - DO NOT pass its return value to more than a single component.
//
// In other words, the only safe way to use it is next to the leaf Reanimated View.
//
// Relevant bug reports:
// - https://github.com/software-mansion/react-native-reanimated/issues/5345
// - https://github.com/software-mansion/react-native-reanimated/issues/5360
// - https://github.com/software-mansion/react-native-reanimated/issues/5364
//
// It's great when it works though.
export {useAnimatedScrollHandler} from 'react-native-reanimated'

View file

@ -1,11 +1,15 @@
import {useState, useCallback} from 'react'
import {useState, useCallback, useMemo} from 'react'
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
import {useShellLayout} from '#/state/shell/shell-layout'
import {s} from 'lib/styles'
import {isWeb} from 'platform/detection'
import {useSharedValue, interpolate, runOnJS} from 'react-native-reanimated'
import {useAnimatedScrollHandler} from './useAnimatedScrollHandler_FIXED'
import {
useSharedValue,
interpolate,
runOnJS,
ScrollHandlers,
} from 'react-native-reanimated'
function clamp(num: number, min: number, max: number) {
'worklet'
@ -15,9 +19,10 @@ function clamp(num: number, min: number, max: number) {
export type OnScrollCb = (
event: NativeSyntheticEvent<NativeScrollEvent>,
) => void
export type OnScrollHandler = ScrollHandlers<any>
export type ResetCb = () => void
export function useOnMainScroll(): [OnScrollCb, boolean, ResetCb] {
export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
const {headerHeight} = useShellLayout()
const [isScrolledDown, setIsScrolledDown] = useState(false)
const mode = useMinimalShellMode()
@ -25,12 +30,18 @@ export function useOnMainScroll(): [OnScrollCb, boolean, ResetCb] {
const startDragOffset = useSharedValue<number | null>(null)
const startMode = useSharedValue<number | null>(null)
const scrollHandler = useAnimatedScrollHandler({
onBeginDrag(e) {
const onBeginDrag = useCallback(
(e: NativeScrollEvent) => {
'worklet'
startDragOffset.value = e.contentOffset.y
startMode.value = mode.value
},
onEndDrag(e) {
[mode, startDragOffset, startMode],
)
const onEndDrag = useCallback(
(e: NativeScrollEvent) => {
'worklet'
startDragOffset.value = null
startMode.value = null
if (e.contentOffset.y < headerHeight.value / 2) {
@ -41,7 +52,12 @@ export function useOnMainScroll(): [OnScrollCb, boolean, ResetCb] {
setMode(Math.round(mode.value) === 1)
}
},
onScroll(e) {
[startDragOffset, startMode, setMode, mode, headerHeight],
)
const onScroll = useCallback(
(e: NativeScrollEvent) => {
'worklet'
// Keep track of whether we want to show "scroll to top".
if (!isScrolledDown && e.contentOffset.y > s.window.height) {
runOnJS(setIsScrolledDown)(true)
@ -86,7 +102,17 @@ export function useOnMainScroll(): [OnScrollCb, boolean, ResetCb] {
startMode.value = mode.value
}
},
})
[headerHeight, mode, setMode, isScrolledDown, startDragOffset, startMode],
)
const scrollHandler: ScrollHandlers<any> = useMemo(
() => ({
onBeginDrag,
onEndDrag,
onScroll,
}),
[onBeginDrag, onEndDrag, onScroll],
)
return [
scrollHandler,