import React, {memo} from 'react' import {FlatListProps, RefreshControl, ViewToken} from 'react-native' import {runOnJS, useSharedValue} from 'react-native-reanimated' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {usePalette} from '#/lib/hooks/usePalette' import {useScrollHandlers} from '#/lib/ScrollContext' import {addStyle} from 'lib/styles' import {FlatList_INTERNAL} from './Views' export type ListMethods = FlatList_INTERNAL export type ListProps = Omit< FlatListProps, | 'onMomentumScrollBegin' // Use ScrollContext instead. | 'onMomentumScrollEnd' // Use ScrollContext instead. | 'onScroll' // Use ScrollContext instead. | 'onScrollBeginDrag' // Use ScrollContext instead. | 'onScrollEndDrag' // Use ScrollContext instead. | 'refreshControl' // Pass refreshing and/or onRefresh instead. | 'contentOffset' // Pass headerOffset instead. > & { onScrolledDownChange?: (isScrolledDown: boolean) => void headerOffset?: number refreshing?: boolean onRefresh?: () => void onItemSeen?: (item: ItemT) => void desktopFixedHeight?: number | boolean // Web only prop to contain the scroll to the container rather than the window disableFullWindowScroll?: boolean sideBorders?: boolean // Web only prop to disable a perf optimization (which would otherwise be on). disableContainStyle?: boolean } export type ListRef = React.MutableRefObject const SCROLLED_DOWN_LIMIT = 200 function ListImpl( { onScrolledDownChange, refreshing, onRefresh, onItemSeen, headerOffset, style, ...props }: ListProps, ref: React.Ref, ) { const isScrolledDown = useSharedValue(false) const pal = usePalette('default') function handleScrolledDownChange(didScrollDown: boolean) { onScrolledDownChange?.(didScrollDown) } // Intentionally destructured outside the main thread closure. // See https://github.com/bluesky-social/social-app/pull/4108. const { onBeginDrag: onBeginDragFromContext, onEndDrag: onEndDragFromContext, onScroll: onScrollFromContext, onMomentumEnd: onMomentumEndFromContext, } = useScrollHandlers() const scrollHandler = useAnimatedScrollHandler({ onBeginDrag(e, ctx) { onBeginDragFromContext?.(e, ctx) }, onEndDrag(e, ctx) { onEndDragFromContext?.(e, ctx) }, onScroll(e, ctx) { onScrollFromContext?.(e, ctx) const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT if (isScrolledDown.value !== didScrollDown) { isScrolledDown.value = didScrollDown if (onScrolledDownChange != null) { runOnJS(handleScrolledDownChange)(didScrollDown) } } }, // Note: adding onMomentumBegin here makes simulator scroll // lag on Android. So either don't add it, or figure out why. onMomentumEnd(e, ctx) { onMomentumEndFromContext?.(e, ctx) }, }) const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => { if (!onItemSeen) { return [undefined, undefined] } return [ (info: {viewableItems: Array; changed: Array}) => { for (const item of info.changed) { if (item.isViewable) { onItemSeen(item.item) } } }, { itemVisiblePercentThreshold: 40, minimumViewTime: 1.5e3, }, ] }, [onItemSeen]) let refreshControl if (refreshing !== undefined || onRefresh !== undefined) { refreshControl = ( ) } let contentOffset if (headerOffset != null) { style = addStyle(style, { paddingTop: headerOffset, }) contentOffset = {x: 0, y: headerOffset * -1} } return ( ) } export const List = memo(React.forwardRef(ListImpl)) as ( props: ListProps & {ref?: React.Ref}, ) => React.ReactElement