Add onScrolledDownChange

This commit is contained in:
Dan Abramov
2023-12-13 04:11:34 +00:00
parent c0eaaf8360
commit b4de3dda7d
6 changed files with 40 additions and 21 deletions
+3 -15
View File
@@ -1,13 +1,11 @@
import {useState, useCallback, useMemo} from 'react' import {useCallback, useMemo} from 'react'
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native' import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
import {useShellLayout} from '#/state/shell/shell-layout' import {useShellLayout} from '#/state/shell/shell-layout'
import {s} from 'lib/styles'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import { import {
useSharedValue, useSharedValue,
interpolate, interpolate,
runOnJS,
ScrollHandlers, ScrollHandlers,
} from 'react-native-reanimated' } from 'react-native-reanimated'
@@ -22,9 +20,8 @@ export type OnScrollCb = (
export type OnScrollHandler = ScrollHandlers<any> export type OnScrollHandler = ScrollHandlers<any>
export type ResetCb = () => void export type ResetCb = () => void
export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] { export function useOnMainScroll(): [OnScrollHandler, ResetCb] {
const {headerHeight} = useShellLayout() const {headerHeight} = useShellLayout()
const [isScrolledDown, setIsScrolledDown] = useState(false)
const mode = useMinimalShellMode() const mode = useMinimalShellMode()
const setMode = useSetMinimalShellMode() const setMode = useSetMinimalShellMode()
const startDragOffset = useSharedValue<number | null>(null) const startDragOffset = useSharedValue<number | null>(null)
@@ -58,13 +55,6 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
const onScroll = useCallback( const onScroll = useCallback(
(e: NativeScrollEvent) => { (e: NativeScrollEvent) => {
'worklet' 'worklet'
// Keep track of whether we want to show "scroll to top".
if (!isScrolledDown && e.contentOffset.y > s.window.height) {
runOnJS(setIsScrolledDown)(true)
} else if (isScrolledDown && e.contentOffset.y < s.window.height) {
runOnJS(setIsScrolledDown)(false)
}
if (startDragOffset.value === null || startMode.value === null) { if (startDragOffset.value === null || startMode.value === null) {
if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) { if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
// If we're close enough to the top, always show the shell. // If we're close enough to the top, always show the shell.
@@ -102,7 +92,7 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
startMode.value = mode.value startMode.value = mode.value
} }
}, },
[headerHeight, mode, setMode, isScrolledDown, startDragOffset, startMode], [headerHeight, mode, setMode, startDragOffset, startMode],
) )
const scrollHandler: ScrollHandlers<any> = useMemo( const scrollHandler: ScrollHandlers<any> = useMemo(
@@ -116,9 +106,7 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
return [ return [
scrollHandler, scrollHandler,
isScrolledDown,
useCallback(() => { useCallback(() => {
setIsScrolledDown(false)
setMode(false) setMode(false)
}, [setMode]), }, [setMode]),
] ]
+3 -1
View File
@@ -52,7 +52,8 @@ export function FeedPage({
const {isDesktop} = useWebMediaQueries() const {isDesktop} = useWebMediaQueries()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const [onMainScroll, resetMainScroll] = useOnMainScroll()
const {screen, track} = useAnalytics() const {screen, track} = useAnalytics()
const headerOffset = useHeaderOffset() const headerOffset = useHeaderOffset()
const scrollElRef = React.useRef<ListMethods>(null) const scrollElRef = React.useRef<ListMethods>(null)
@@ -173,6 +174,7 @@ export function FeedPage({
pollInterval={POLL_FREQ} pollInterval={POLL_FREQ}
scrollElRef={scrollElRef} scrollElRef={scrollElRef}
onScroll={onMainScroll} onScroll={onMainScroll}
onScrolledDownChange={setIsScrolledDown}
onHasNew={setHasNew} onHasNew={setHasNew}
scrollEventThrottle={1} scrollEventThrottle={1}
renderEmptyState={renderEmptyState} renderEmptyState={renderEmptyState}
+3
View File
@@ -25,11 +25,13 @@ export function Feed({
scrollElRef, scrollElRef,
onPressTryAgain, onPressTryAgain,
onScroll, onScroll,
onScrolledDownChange,
ListHeaderComponent, ListHeaderComponent,
}: { }: {
scrollElRef?: ListRef scrollElRef?: ListRef
onPressTryAgain?: () => void onPressTryAgain?: () => void
onScroll?: OnScrollHandler onScroll?: OnScrollHandler
onScrolledDownChange: (isScrolledDown: boolean) => void
ListHeaderComponent?: () => JSX.Element ListHeaderComponent?: () => JSX.Element
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
@@ -166,6 +168,7 @@ export function Feed({
onEndReached={onEndReached} onEndReached={onEndReached}
onEndReachedThreshold={0.6} onEndReachedThreshold={0.6}
onScroll={scrollHandler} onScroll={scrollHandler}
onScrolledDownChange={onScrolledDownChange}
scrollEventThrottle={1} scrollEventThrottle={1}
contentContainerStyle={s.contentContainer} contentContainerStyle={s.contentContainer}
// @ts-ignore our .web version only -prf // @ts-ignore our .web version only -prf
+3
View File
@@ -46,6 +46,7 @@ let Feed = ({
pollInterval, pollInterval,
scrollElRef, scrollElRef,
onScroll, onScroll,
onScrolledDownChange,
onHasNew, onHasNew,
scrollEventThrottle, scrollEventThrottle,
renderEmptyState, renderEmptyState,
@@ -65,6 +66,7 @@ let Feed = ({
scrollElRef?: ListRef scrollElRef?: ListRef
onHasNew?: (v: boolean) => void onHasNew?: (v: boolean) => void
onScroll?: OnScrollHandler onScroll?: OnScrollHandler
onScrolledDownChange?: (isScrolledDown: boolean) => void
scrollEventThrottle?: number scrollEventThrottle?: number
renderEmptyState: () => JSX.Element renderEmptyState: () => JSX.Element
renderEndOfFeed?: () => JSX.Element renderEndOfFeed?: () => JSX.Element
@@ -295,6 +297,7 @@ let Feed = ({
}} }}
style={{paddingTop: headerOffset}} style={{paddingTop: headerOffset}}
onScroll={onScroll != null ? scrollHandler : undefined} onScroll={onScroll != null ? scrollHandler : undefined}
onScrolledDownChange={onScrolledDownChange}
scrollEventThrottle={scrollEventThrottle} scrollEventThrottle={scrollEventThrottle}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
onEndReached={onEndReached} onEndReached={onEndReached}
+25 -4
View File
@@ -1,13 +1,34 @@
import React from 'react' import React, {useState} from 'react'
import {FlatListProps} from 'react-native' import {FlatListProps} from 'react-native'
import {FlatList_INTERNAL} from './Views' import {FlatList_INTERNAL} from './Views'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
export type ListMethods = FlatList_INTERNAL export type ListMethods = FlatList_INTERNAL
export type ListProps<ItemT> = FlatListProps<ItemT> export type ListProps<ItemT> = FlatListProps<ItemT> & {
onScrolledDownChange?: (isScrolledDown: boolean) => void
}
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null> export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
function ListImpl<ItemT>(props: ListProps<ItemT>, ref: React.Ref<ListMethods>) { const SCROLLED_DOWN_LIMIT = 200
return <FlatList_INTERNAL {...props} ref={ref} />
function ListImpl<ItemT>(
{onScrolledDownChange, ...props}: ListProps<ItemT>,
ref: React.Ref<ListMethods>,
) {
const [isScrolledDown, setIsScrolledDown] = useState(false)
// TODO: This ignores the passed-in onScroll completely.
const scrollHandler = useAnimatedScrollHandler({
onScroll(e) {
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
if (isScrolledDown !== didScrollDown) {
setIsScrolledDown(didScrollDown)
onScrolledDownChange?.(didScrollDown)
}
},
})
return <FlatList_INTERNAL {...props} onScroll={scrollHandler} ref={ref} />
} }
export const List = React.forwardRef(ListImpl) as <ItemT>( export const List = React.forwardRef(ListImpl) as <ItemT>(
+3 -1
View File
@@ -36,7 +36,8 @@ type Props = NativeStackScreenProps<
export function NotificationsScreen({}: Props) { export function NotificationsScreen({}: Props) {
const {_} = useLingui() const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() const [onMainScroll, resetMainScroll] = useOnMainScroll()
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const scrollElRef = React.useRef<ListMethods>(null) const scrollElRef = React.useRef<ListMethods>(null)
const checkLatestRef = React.useRef<() => void | null>() const checkLatestRef = React.useRef<() => void | null>()
const {screen} = useAnalytics() const {screen} = useAnalytics()
@@ -133,6 +134,7 @@ export function NotificationsScreen({}: Props) {
<ViewHeader title={_(msg`Notifications`)} canGoBack={false} /> <ViewHeader title={_(msg`Notifications`)} canGoBack={false} />
<Feed <Feed
onScroll={onMainScroll} onScroll={onMainScroll}
onScrolledDownChange={setIsScrolledDown}
scrollElRef={scrollElRef} scrollElRef={scrollElRef}
ListHeaderComponent={ListHeaderComponent} ListHeaderComponent={ListHeaderComponent}
/> />