handle off screen visibility observer.
This commit is contained in:
@@ -25,7 +25,6 @@ import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
|||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import {FeedItem} from './FeedItem'
|
import {FeedItem} from './FeedItem'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
import hairlineWidth = StyleSheet.hairlineWidth
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
|
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||||
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
||||||
@@ -183,15 +182,7 @@ export function Feed({
|
|||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={
|
onEndReachedThreshold={2}
|
||||||
/*
|
|
||||||
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
|
|
||||||
}
|
|
||||||
onScrolledDownChange={onScrolledDownChange}
|
onScrolledDownChange={onScrolledDownChange}
|
||||||
contentContainerStyle={s.contentContainer}
|
contentContainerStyle={s.contentContainer}
|
||||||
// @ts-ignore our .web version only -prf
|
// @ts-ignore our .web version only -prf
|
||||||
|
|||||||
@@ -63,53 +63,7 @@ function ListImpl<ItemT>(
|
|||||||
}: ListProps<ItemT>,
|
}: ListProps<ItemT>,
|
||||||
ref: React.Ref<ListMethods>,
|
ref: React.Ref<ListMethods>,
|
||||||
) {
|
) {
|
||||||
const contextScrollHandlers = useScrollHandlers()
|
const [prevDataLength, setPrevDataLength] = React.useState(data?.length)
|
||||||
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 = <ListHeaderComponent />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let footerComponent: JSX.Element | null = null
|
|
||||||
if (ListFooterComponent != null) {
|
|
||||||
if (isValidElement(ListFooterComponent)) {
|
|
||||||
footerComponent = ListFooterComponent
|
|
||||||
} else {
|
|
||||||
// @ts-ignore Nah it's fine.
|
|
||||||
footerComponent = <ListFooterComponent />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let emptyComponent: JSX.Element | null = null
|
|
||||||
if (ListEmptyComponent != null) {
|
|
||||||
if (isValidElement(ListEmptyComponent)) {
|
|
||||||
emptyComponent = ListEmptyComponent
|
|
||||||
} else {
|
|
||||||
// @ts-ignore Nah it's fine.
|
|
||||||
emptyComponent = <ListEmptyComponent />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headerOffset != null) {
|
|
||||||
style = addStyle(style, {
|
|
||||||
paddingTop: headerOffset,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getScrollableNode = React.useCallback(() => {
|
const getScrollableNode = React.useCallback(() => {
|
||||||
if (containWeb) {
|
if (containWeb) {
|
||||||
@@ -184,6 +138,77 @@ function ListImpl<ItemT>(
|
|||||||
}
|
}
|
||||||
}, [containWeb])
|
}, [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 = <ListHeaderComponent />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let footerComponent: JSX.Element | null = null
|
||||||
|
if (ListFooterComponent != null) {
|
||||||
|
if (isValidElement(ListFooterComponent)) {
|
||||||
|
footerComponent = ListFooterComponent
|
||||||
|
} else {
|
||||||
|
// @ts-ignore Nah it's fine.
|
||||||
|
footerComponent = <ListFooterComponent />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let emptyComponent: JSX.Element | null = null
|
||||||
|
if (ListEmptyComponent != null) {
|
||||||
|
if (isValidElement(ListEmptyComponent)) {
|
||||||
|
emptyComponent = ListEmptyComponent
|
||||||
|
} else {
|
||||||
|
// @ts-ignore Nah it's fine.
|
||||||
|
emptyComponent = <ListEmptyComponent />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerOffset != null) {
|
||||||
|
style = addStyle(style, {
|
||||||
|
paddingTop: headerOffset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const nativeRef = React.useRef<HTMLDivElement>(null)
|
const nativeRef = React.useRef<HTMLDivElement>(null)
|
||||||
React.useImperativeHandle(
|
React.useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
|
|||||||
Reference in New Issue
Block a user