diff options
Diffstat (limited to 'src/view/com/posts/Feed.tsx')
-rw-r--r-- | src/view/com/posts/Feed.tsx | 182 |
1 files changed, 130 insertions, 52 deletions
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 1ecb14912..f0f7cd919 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -1,7 +1,7 @@ -import React, {MutableRefObject} from 'react' -import {observer} from 'mobx-react-lite' +import React, {memo, MutableRefObject} from 'react' import { ActivityIndicator, + Dimensions, RefreshControl, StyleProp, StyleSheet, @@ -11,26 +11,36 @@ import { import {FlatList} from '../util/Views' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {FeedErrorMessage} from './FeedErrorMessage' -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 {OnScrollHandler} from 'lib/hooks/useOnMainScroll' import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' +import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {useTheme} from 'lib/ThemeContext' import {logger} from '#/logger' +import { + FeedDescriptor, + FeedParams, + usePostFeedQuery, + pollLatest, +} from '#/state/queries/post-feed' +import {useModerationOpts} from '#/state/queries/preferences' 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({ +let Feed = ({ feed, + feedParams, style, + enabled, + pollInterval, scrollElRef, onScroll, + onHasNew, scrollEventThrottle, renderEmptyState, renderEndOfFeed, @@ -40,10 +50,14 @@ export const Feed = observer(function Feed({ ListHeaderComponent, extraData, }: { - feed: PostsFeedModel + feed: FeedDescriptor + feedParams?: FeedParams style?: StyleProp<ViewStyle> + enabled?: boolean + pollInterval?: number scrollElRef?: MutableRefObject<FlatList<any> | null> - onScroll?: OnScrollCb + onHasNew?: (v: boolean) => void + onScroll?: OnScrollHandler scrollEventThrottle?: number renderEmptyState: () => JSX.Element renderEndOfFeed?: () => JSX.Element @@ -52,70 +66,110 @@ export const Feed = observer(function Feed({ desktopFixedHeightOffset?: number ListHeaderComponent?: () => JSX.Element extraData?: any -}) { +}): React.ReactNode => { const pal = usePalette('default') const theme = useTheme() const {track} = useAnalytics() - const [isRefreshing, setIsRefreshing] = React.useState(false) + const [isPTRing, setIsPTRing] = React.useState(false) + const checkForNewRef = React.useRef<(() => void) | null>(null) + + const moderationOpts = useModerationOpts() + const opts = React.useMemo(() => ({enabled}), [enabled]) + const { + data, + isFetching, + isFetched, + isError, + error, + refetch, + hasNextPage, + isFetchingNextPage, + fetchNextPage, + } = usePostFeedQuery(feed, feedParams, opts) + const isEmpty = !isFetching && !data?.pages[0]?.slices.length + + const checkForNew = React.useCallback(async () => { + if (!data?.pages[0] || isFetching || !onHasNew || !enabled) { + return + } + try { + if (await pollLatest(data.pages[0])) { + onHasNew(true) + } + } catch (e) { + logger.error('Poll latest failed', {feed, error: String(e)}) + } + }, [feed, data, isFetching, onHasNew, enabled]) + + React.useEffect(() => { + // we store the interval handler in a ref to avoid needless + // reassignments of the interval + checkForNewRef.current = checkForNew + }, [checkForNew]) + React.useEffect(() => { + if (!pollInterval) { + return + } + const i = setInterval(() => checkForNewRef.current?.(), pollInterval) + return () => clearInterval(i) + }, [pollInterval]) - const data = React.useMemo(() => { - let feedItems: any[] = [] - if (feed.hasLoaded) { - if (feed.hasError) { - feedItems = feedItems.concat([ERROR_ITEM]) + const feedItems = React.useMemo(() => { + let arr: any[] = [] + if (isFetched && moderationOpts) { + if (isError && isEmpty) { + arr = arr.concat([ERROR_ITEM]) } - if (feed.isEmpty) { - feedItems = feedItems.concat([EMPTY_FEED_ITEM]) - } else { - feedItems = feedItems.concat(feed.slices) + if (isEmpty) { + arr = arr.concat([EMPTY_FEED_ITEM]) + } else if (data) { + for (const page of data?.pages) { + arr = arr.concat(page.slices) + } } - if (feed.loadMoreError) { - feedItems = feedItems.concat([LOAD_MORE_ERROR_ITEM]) + if (isError && !isEmpty) { + arr = arr.concat([LOAD_MORE_ERROR_ITEM]) } } else { - feedItems.push(LOADING_ITEM) + arr.push(LOADING_ITEM) } - return feedItems - }, [ - feed.hasError, - feed.hasLoaded, - feed.isEmpty, - feed.slices, - feed.loadMoreError, - ]) + return arr + }, [isFetched, isError, isEmpty, data, moderationOpts]) // events // = const onRefresh = React.useCallback(async () => { track('Feed:onRefresh') - setIsRefreshing(true) + setIsPTRing(true) try { - await feed.refresh() + await refetch() + onHasNew?.(false) } catch (err) { logger.error('Failed to refresh posts feed', {error: err}) } - setIsRefreshing(false) - }, [feed, track, setIsRefreshing]) + setIsPTRing(false) + }, [refetch, track, setIsPTRing, onHasNew]) const onEndReached = React.useCallback(async () => { - if (!feed.hasLoaded || !feed.hasMore) return + if (isFetching || !hasNextPage || isError) return track('Feed:onEndReached') try { - await feed.loadMore() + await fetchNextPage() } catch (err) { logger.error('Failed to load more posts', {error: err}) } - }, [feed, track]) + }, [isFetching, hasNextPage, isError, fetchNextPage, track]) const onPressTryAgain = React.useCallback(() => { - feed.refresh() - }, [feed]) + refetch() + onHasNew?.(false) + }, [refetch, onHasNew]) const onPressRetryLoadMore = React.useCallback(() => { - feed.retryLoadMore() - }, [feed]) + fetchNextPage() + }, [fetchNextPage]) // rendering // = @@ -126,7 +180,11 @@ export const Feed = observer(function Feed({ return renderEmptyState() } else if (item === ERROR_ITEM) { return ( - <FeedErrorMessage feed={feed} onPressTryAgain={onPressTryAgain} /> + <FeedErrorMessage + feedDesc={feed} + error={error} + onPressTryAgain={onPressTryAgain} + /> ) } else if (item === LOAD_MORE_ERROR_ITEM) { return ( @@ -138,47 +196,65 @@ export const Feed = observer(function Feed({ } else if (item === LOADING_ITEM) { return <PostFeedLoadingPlaceholder /> } - return <FeedSlice slice={item} /> + return ( + <FeedSlice + slice={item} + // we check for this before creating the feedItems array + moderationOpts={moderationOpts!} + /> + ) }, - [feed, onPressTryAgain, onPressRetryLoadMore, renderEmptyState], + [ + feed, + error, + onPressTryAgain, + onPressRetryLoadMore, + renderEmptyState, + moderationOpts, + ], ) + const shouldRenderEndOfFeed = + !hasNextPage && !isEmpty && !isFetching && !isError && !!renderEndOfFeed const FeedFooter = React.useCallback( () => - feed.isLoadingMore ? ( + isFetchingNextPage ? ( <View style={styles.feedFooter}> <ActivityIndicator /> </View> - ) : !feed.hasMore && !feed.isEmpty && renderEndOfFeed ? ( + ) : shouldRenderEndOfFeed ? ( renderEndOfFeed() ) : ( <View /> ), - [feed.isLoadingMore, feed.hasMore, feed.isEmpty, renderEndOfFeed], + [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed], ) + const scrollHandler = useAnimatedScrollHandler(onScroll || {}) return ( <View testID={testID} style={style}> <FlatList testID={testID ? `${testID}-flatlist` : undefined} ref={scrollElRef} - data={data} + data={feedItems} keyExtractor={item => item._reactKey} renderItem={renderItem} ListFooterComponent={FeedFooter} ListHeaderComponent={ListHeaderComponent} refreshControl={ <RefreshControl - refreshing={isRefreshing} + refreshing={isPTRing} onRefresh={onRefresh} tintColor={pal.colors.text} titleColor={pal.colors.text} progressViewOffset={headerOffset} /> } - contentContainerStyle={s.contentContainer} + contentContainerStyle={{ + minHeight: Dimensions.get('window').height * 1.5, + }} style={{paddingTop: headerOffset}} - onScroll={onScroll} + onScroll={onScroll != null ? scrollHandler : undefined} scrollEventThrottle={scrollEventThrottle} indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} onEndReached={onEndReached} @@ -193,7 +269,9 @@ export const Feed = observer(function Feed({ /> </View> ) -}) +} +Feed = memo(Feed) +export {Feed} const styles = StyleSheet.create({ feedFooter: {paddingTop: 20}, |