import React, {MutableRefObject} from 'react' import {CenteredView, FlatList} from '../util/Views' import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native' import {FeedItem} from './FeedItem' import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {ErrorMessage} from '../util/error/ErrorMessage' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {EmptyState} from '../util/EmptyState' import {OnScrollHandler} from 'lib/hooks/useOnMainScroll' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {s} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {useNotificationFeedQuery} from '#/state/queries/notifications/feed' import {useUnreadNotificationsApi} from '#/state/queries/notifications/unread' import {logger} from '#/logger' import {cleanError} from '#/lib/strings/errors' import {useModerationOpts} from '#/state/queries/preferences' const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} const LOADING_ITEM = {_reactKey: '__loading__'} export function Feed({ scrollElRef, onPressTryAgain, onScroll, ListHeaderComponent, }: { scrollElRef?: MutableRefObject | null> onPressTryAgain?: () => void onScroll?: OnScrollHandler ListHeaderComponent?: () => JSX.Element }) { const pal = usePalette('default') const [isPTRing, setIsPTRing] = React.useState(false) const moderationOpts = useModerationOpts() const {markAllRead} = useUnreadNotificationsApi() const { data, isLoading, isFetching, isFetched, isError, error, refetch, hasNextPage, isFetchingNextPage, fetchNextPage, } = useNotificationFeedQuery({enabled: !!moderationOpts}) const isEmpty = !isFetching && !data?.pages[0]?.items.length const firstItem = data?.pages[0]?.items[0] // mark all read on fresh data React.useEffect(() => { let cleanup if (firstItem) { const to = setTimeout(() => markAllRead(), 250) cleanup = () => clearTimeout(to) } return cleanup }, [firstItem, markAllRead]) const items = React.useMemo(() => { let arr: any[] = [] if (isFetched) { if (isEmpty) { arr = arr.concat([EMPTY_FEED_ITEM]) } else if (data) { for (const page of data?.pages) { arr = arr.concat(page.items) } } if (isError && !isEmpty) { arr = arr.concat([LOAD_MORE_ERROR_ITEM]) } } else { arr.push(LOADING_ITEM) } return arr }, [isFetched, isError, isEmpty, data]) const onRefresh = React.useCallback(async () => { try { setIsPTRing(true) await refetch() } catch (err) { logger.error('Failed to refresh notifications feed', { error: err, }) } finally { setIsPTRing(false) } }, [refetch, setIsPTRing]) const onEndReached = React.useCallback(async () => { if (isFetching || !hasNextPage || isError) return try { await fetchNextPage() } catch (err) { logger.error('Failed to load more notifications', {error: err}) } }, [isFetching, hasNextPage, isError, fetchNextPage]) const onPressRetryLoadMore = React.useCallback(() => { fetchNextPage() }, [fetchNextPage]) // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf // VirtualizedList: You have a large list that is slow to update - make sure your // renderItem function renders components that follow React performance best practices // like PureComponent, shouldComponentUpdate, etc const renderItem = React.useCallback( ({item}: {item: any}) => { if (item === EMPTY_FEED_ITEM) { return ( ) } else if (item === LOAD_MORE_ERROR_ITEM) { return ( ) } else if (item === LOADING_ITEM) { return } return }, [onPressRetryLoadMore, moderationOpts], ) const showHeaderSpinner = !isPTRing && isFetching && !isLoading const FeedHeader = React.useCallback( () => ( {ListHeaderComponent ? : null} {showHeaderSpinner ? ( ) : null} ), [ListHeaderComponent, showHeaderSpinner], ) const FeedFooter = React.useCallback( () => isFetchingNextPage ? ( ) : ( ), [isFetchingNextPage], ) const scrollHandler = useAnimatedScrollHandler(onScroll || {}) return ( {error && ( )} item._reactKey} renderItem={renderItem} ListHeaderComponent={FeedHeader} ListFooterComponent={FeedFooter} refreshControl={ } onEndReached={onEndReached} onEndReachedThreshold={0.6} onScroll={scrollHandler} scrollEventThrottle={1} contentContainerStyle={s.contentContainer} // @ts-ignore our .web version only -prf desktopFixedHeight /> ) } const styles = StyleSheet.create({ feedFooter: {paddingTop: 20}, emptyState: {paddingVertical: 40}, })