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.tsx182
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},