From e499ea0ed66b31964f79261b41f58a288b0cdb6f Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 4 Jul 2024 09:20:33 -0700 Subject: [PATCH] handle off screen visibility observer. --- src/view/com/notifications/Feed.tsx | 11 +-- src/view/com/util/List.web.tsx | 119 +++++++++++++++++----------- 2 files changed, 73 insertions(+), 57 deletions(-) diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx index 90f2785b21..e2f12e84f1 100644 --- a/src/view/com/notifications/Feed.tsx +++ b/src/view/com/notifications/Feed.tsx @@ -25,7 +25,6 @@ import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' import {CenteredView} from '#/view/com/util/Views' import {FeedItem} from './FeedItem' import hairlineWidth = StyleSheet.hairlineWidth -import {isWeb} from '#/platform/detection' const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} @@ -183,15 +182,7 @@ export function Feed({ refreshing={isPTRing} onRefresh={onRefresh} onEndReached={onEndReached} - onEndReachedThreshold={ - /* - NOTE: - web's intersection observer struggles with the 2x threshold - and leads to missed pagination, so we keep it <1 - -prf - */ - isWeb ? 0.6 : 2 - } + onEndReachedThreshold={2} onScrolledDownChange={onScrolledDownChange} contentContainerStyle={s.contentContainer} // @ts-ignore our .web version only -prf diff --git a/src/view/com/util/List.web.tsx b/src/view/com/util/List.web.tsx index e917ab1d32..6493abf652 100644 --- a/src/view/com/util/List.web.tsx +++ b/src/view/com/util/List.web.tsx @@ -63,53 +63,7 @@ function ListImpl( }: ListProps, ref: React.Ref, ) { - const contextScrollHandlers = useScrollHandlers() - const pal = usePalette('default') - const {isMobile} = useWebMediaQueries() - if (!isMobile) { - contentContainerStyle = addStyle( - contentContainerStyle, - styles.containerScroll, - ) - } - - const isEmpty = !data || data.length === 0 - - let headerComponent: JSX.Element | null = null - if (ListHeaderComponent != null) { - if (isValidElement(ListHeaderComponent)) { - headerComponent = ListHeaderComponent - } else { - // @ts-ignore Nah it's fine. - headerComponent = - } - } - - let footerComponent: JSX.Element | null = null - if (ListFooterComponent != null) { - if (isValidElement(ListFooterComponent)) { - footerComponent = ListFooterComponent - } else { - // @ts-ignore Nah it's fine. - footerComponent = - } - } - - let emptyComponent: JSX.Element | null = null - if (ListEmptyComponent != null) { - if (isValidElement(ListEmptyComponent)) { - emptyComponent = ListEmptyComponent - } else { - // @ts-ignore Nah it's fine. - emptyComponent = - } - } - - if (headerOffset != null) { - style = addStyle(style, { - paddingTop: headerOffset, - }) - } + const [prevDataLength, setPrevDataLength] = React.useState(data?.length) const getScrollableNode = React.useCallback(() => { if (containWeb) { @@ -184,6 +138,77 @@ function ListImpl( } }, [containWeb]) + // Whenever we get new data, we want to make sure that the onEndReached threshold will be *below* the current scroll + // bottom position. If it is not the IntersectionObserver will never fire the onEndReached threshold for us - since + // we've already scrolled past the component. + const callOnEndReachedIfNeeded = () => { + const node = getScrollableNode() + if (!node || !onEndReached || !onEndReachedThreshold || !data?.length) { + return + } + + const scrollViewHeight = node.clientHeight + const scrollContainerHeight = node.scrollHeight + const scrollPos = node.scrollY + const thresholdPx = onEndReachedThreshold * scrollViewHeight + + if (scrollContainerHeight - thresholdPx <= scrollPos) { + onEndReached({distanceFromEnd: 0}) + } + setPrevDataLength(data?.length) + } + if (data?.length !== prevDataLength) { + callOnEndReachedIfNeeded() + } + + const contextScrollHandlers = useScrollHandlers() + const pal = usePalette('default') + const {isMobile} = useWebMediaQueries() + if (!isMobile) { + contentContainerStyle = addStyle( + contentContainerStyle, + styles.containerScroll, + ) + } + + const isEmpty = !data || data.length === 0 + + let headerComponent: JSX.Element | null = null + if (ListHeaderComponent != null) { + if (isValidElement(ListHeaderComponent)) { + headerComponent = ListHeaderComponent + } else { + // @ts-ignore Nah it's fine. + headerComponent = + } + } + + let footerComponent: JSX.Element | null = null + if (ListFooterComponent != null) { + if (isValidElement(ListFooterComponent)) { + footerComponent = ListFooterComponent + } else { + // @ts-ignore Nah it's fine. + footerComponent = + } + } + + let emptyComponent: JSX.Element | null = null + if (ListEmptyComponent != null) { + if (isValidElement(ListEmptyComponent)) { + emptyComponent = ListEmptyComponent + } else { + // @ts-ignore Nah it's fine. + emptyComponent = + } + } + + if (headerOffset != null) { + style = addStyle(style, { + paddingTop: headerOffset, + }) + } + const nativeRef = React.useRef(null) React.useImperativeHandle( ref,