import React, {MutableRefObject} from 'react' import {observer} from 'mobx-react-lite' import { ActivityIndicator, RefreshControl, StyleProp, StyleSheet, View, ViewStyle, } from 'react-native' import {FlatList} from '../util/Views' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {ErrorMessage} from '../util/error/ErrorMessage' import {PostsFeedModel} from 'state/models/feeds/posts' import {FeedSlice} from './FeedSlice' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {OnScrollCb} from 'lib/hooks/useOnMainScroll' import {s} from 'lib/styles' import {useAnalytics} from 'lib/analytics' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' const LOADING_ITEM = {_reactKey: '__loading__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const ERROR_ITEM = {_reactKey: '__error__'} const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} export const Feed = observer(function Feed({ feed, style, showPostFollowBtn, scrollElRef, onPressTryAgain, onScroll, scrollEventThrottle, onMomentumScrollEnd, renderEmptyState, testID, headerOffset = 0, ListHeaderComponent, extraData, }: { feed: PostsFeedModel style?: StyleProp showPostFollowBtn?: boolean scrollElRef?: MutableRefObject | null> onPressTryAgain?: () => void onScroll?: OnScrollCb scrollEventThrottle?: number renderEmptyState?: () => JSX.Element testID?: string headerOffset?: number ListHeaderComponent?: () => JSX.Element extraData?: any }) { const pal = usePalette('default') const theme = useTheme() const {track} = useAnalytics() const [isRefreshing, setIsRefreshing] = React.useState(false) const data = React.useMemo(() => { let feedItems: any[] = [] if (feed.hasLoaded) { if (feed.hasError) { feedItems = feedItems.concat([ERROR_ITEM]) } if (feed.isEmpty) { feedItems = feedItems.concat([EMPTY_FEED_ITEM]) } else { feedItems = feedItems.concat(feed.slices) } if (feed.loadMoreError) { feedItems = feedItems.concat([LOAD_MORE_ERROR_ITEM]) } } else if (feed.isLoading) { feedItems = feedItems.concat([LOADING_ITEM]) } return feedItems }, [ feed.hasError, feed.hasLoaded, feed.isLoading, feed.isEmpty, feed.slices, feed.loadMoreError, ]) // events // = const onRefresh = React.useCallback(async () => { track('Feed:onRefresh') setIsRefreshing(true) try { await feed.refresh() } catch (err) { feed.rootStore.log.error('Failed to refresh posts feed', err) } setIsRefreshing(false) }, [feed, track, setIsRefreshing]) const onEndReached = React.useCallback(async () => { track('Feed:onEndReached') try { await feed.loadMore() } catch (err) { feed.rootStore.log.error('Failed to load more posts', err) } }, [feed, track]) const onPressRetryLoadMore = React.useCallback(() => { feed.retryLoadMore() }, [feed]) // rendering // = const renderItem = React.useCallback( ({item}: {item: any}) => { if (item === EMPTY_FEED_ITEM) { if (renderEmptyState) { return renderEmptyState() } return } else if (item === ERROR_ITEM) { return ( ) } else if (item === LOAD_MORE_ERROR_ITEM) { return ( ) } else if (item === LOADING_ITEM) { return } return }, [ feed, onPressTryAgain, onPressRetryLoadMore, showPostFollowBtn, renderEmptyState, ], ) const FeedFooter = React.useCallback( () => feed.isLoading ? ( ) : ( ), [feed], ) return ( {data.length > 0 && ( item._reactKey} renderItem={renderItem} ListFooterComponent={FeedFooter} ListHeaderComponent={ListHeaderComponent} refreshControl={ } contentContainerStyle={s.contentContainer} style={{paddingTop: headerOffset}} onScroll={onScroll} scrollEventThrottle={scrollEventThrottle} onMomentumScrollEnd={onMomentumScrollEnd} indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} onEndReached={onEndReached} onEndReachedThreshold={0.6} removeClippedSubviews={true} contentOffset={{x: 0, y: headerOffset * -1}} extraData={extraData} // @ts-ignore our .web version only -prf desktopFixedHeight /> )} ) }) const styles = StyleSheet.create({ feedFooter: {paddingTop: 20}, })