about summary refs log tree commit diff
path: root/src/view/com/posts/Feed.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/posts/Feed.tsx')
-rw-r--r--src/view/com/posts/Feed.tsx144
1 files changed, 103 insertions, 41 deletions
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index 7e28712ee..50afc1950 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -1,5 +1,4 @@
 import React, {MutableRefObject} from 'react'
-import {observer} from 'mobx-react-lite'
 import {
   ActivityIndicator,
   Dimensions,
@@ -12,7 +11,6 @@ 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 {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
@@ -21,17 +19,26 @@ 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,
+} from '#/state/queries/post-feed'
 
 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({
+export function Feed({
   feed,
+  feedParams,
   style,
+  enabled,
+  pollInterval,
   scrollElRef,
   onScroll,
+  onHasNew,
   scrollEventThrottle,
   renderEmptyState,
   renderEndOfFeed,
@@ -41,9 +48,13 @@ 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>
+  onHasNew?: (v: boolean) => void
   onScroll?: OnScrollHandler
   scrollEventThrottle?: number
   renderEmptyState: () => JSX.Element
@@ -58,32 +69,68 @@ export const Feed = observer(function Feed({
   const theme = useTheme()
   const {track} = useAnalytics()
   const [isRefreshing, setIsRefreshing] = React.useState(false)
+  const checkForNewRef = React.useRef<(() => void) | null>(null)
+
+  const opts = React.useMemo(() => ({enabled}), [enabled])
+  const {
+    data,
+    dataUpdatedAt,
+    isFetching,
+    isFetched,
+    isError,
+    error,
+    refetch,
+    hasNextPage,
+    isFetchingNextPage,
+    fetchNextPage,
+    pollLatest,
+  } = usePostFeedQuery(feed, feedParams, opts)
+  const isEmpty = !isFetching && !data?.pages[0]?.slices.length
 
-  const data = React.useMemo(() => {
-    let feedItems: any[] = []
-    if (feed.hasLoaded) {
-      if (feed.hasError) {
-        feedItems = feedItems.concat([ERROR_ITEM])
+  const checkForNew = React.useCallback(async () => {
+    if (!isFetched || isFetching || !onHasNew) {
+      return
+    }
+    try {
+      if (await pollLatest()) {
+        onHasNew(true)
       }
-      if (feed.isEmpty) {
-        feedItems = feedItems.concat([EMPTY_FEED_ITEM])
-      } else {
-        feedItems = feedItems.concat(feed.slices)
+    } catch (e) {
+      logger.error('Poll latest failed', {feed, error: String(e)})
+    }
+  }, [feed, isFetched, isFetching, pollLatest, onHasNew])
+
+  React.useEffect(() => {
+    // we store the interval handler in a ref to avoid needless
+    // reassignments of the interval
+    checkForNewRef.current = checkForNew
+  }, [checkForNew])
+  React.useEffect(() => {
+    const i = setInterval(() => checkForNewRef.current?.(), pollInterval)
+    return () => clearInterval(i)
+  }, [pollInterval])
+
+  const feedItems = React.useMemo(() => {
+    let arr: any[] = []
+    if (isFetched) {
+      if (isError && isEmpty) {
+        arr = arr.concat([ERROR_ITEM])
+      }
+      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])
 
   // events
   // =
@@ -92,31 +139,33 @@ export const Feed = observer(function Feed({
     track('Feed:onRefresh')
     setIsRefreshing(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])
+  }, [refetch, track, setIsRefreshing, 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
   // =
@@ -127,7 +176,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 (
@@ -139,23 +192,32 @@ export const Feed = observer(function Feed({
       } else if (item === LOADING_ITEM) {
         return <PostFeedLoadingPlaceholder />
       }
-      return <FeedSlice slice={item} />
+      return <FeedSlice slice={item} dataUpdatedAt={dataUpdatedAt} />
     },
-    [feed, onPressTryAgain, onPressRetryLoadMore, renderEmptyState],
+    [
+      feed,
+      dataUpdatedAt,
+      error,
+      onPressTryAgain,
+      onPressRetryLoadMore,
+      renderEmptyState,
+    ],
   )
 
+  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 || {})
@@ -164,7 +226,7 @@ export const Feed = observer(function Feed({
       <FlatList
         testID={testID ? `${testID}-flatlist` : undefined}
         ref={scrollElRef}
-        data={data}
+        data={feedItems}
         keyExtractor={item => item._reactKey}
         renderItem={renderItem}
         ListFooterComponent={FeedFooter}
@@ -197,7 +259,7 @@ export const Feed = observer(function Feed({
       />
     </View>
   )
-})
+}
 
 const styles = StyleSheet.create({
   feedFooter: {paddingTop: 20},