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.tsx290
1 files changed, 156 insertions, 134 deletions
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index 905c1e0e0..c623234b8 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -16,10 +16,10 @@ import {useQueryClient} from '@tanstack/react-query'
 
 import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants'
 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
-import {logEvent, useGate} from '#/lib/statsig/statsig'
+import {logEvent} from '#/lib/statsig/statsig'
 import {useTheme} from '#/lib/ThemeContext'
 import {logger} from '#/logger'
-import {isWeb} from '#/platform/detection'
+import {isIOS, isWeb} from '#/platform/detection'
 import {listenPostCreated} from '#/state/events'
 import {useFeedFeedbackContext} from '#/state/feed-feedback'
 import {STALE} from '#/state/queries'
@@ -32,20 +32,17 @@ import {
   usePostFeedQuery,
 } from '#/state/queries/post-feed'
 import {useSession} from '#/state/session'
-import {
-  ProgressGuide,
-  SuggestedFeeds,
-  SuggestedFollows,
-} from '#/components/FeedInterstitials'
+import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials'
 import {List, ListRef} from '../util/List'
 import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
 import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
 import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
 import {FeedErrorMessage} from './FeedErrorMessage'
+import {FeedItem} from './FeedItem'
 import {FeedShutdownMsg} from './FeedShutdownMsg'
-import {FeedSlice} from './FeedSlice'
+import {ViewFullThread} from './ViewFullThread'
 
-type FeedItem =
+type FeedRow =
   | {
       type: 'loading'
       key: string
@@ -72,76 +69,29 @@ type FeedItem =
       slice: FeedPostSlice
     }
   | {
-      type: 'interstitialFeeds'
+      type: 'sliceItem'
       key: string
-      params: {
-        variant: 'default' | string
-      }
-      slot: number
+      slice: FeedPostSlice
+      indexInSlice: number
+      showReplyTo: boolean
+    }
+  | {
+      type: 'sliceViewFullThread'
+      key: string
+      uri: string
     }
   | {
       type: 'interstitialFollows'
       key: string
-      params: {
-        variant: 'default' | string
-      }
-      slot: number
     }
   | {
       type: 'interstitialProgressGuide'
       key: string
-      params: {
-        variant: 'default' | string
-      }
-      slot: number
     }
 
-const feedInterstitialType = 'interstitialFeeds'
-const followInterstitialType = 'interstitialFollows'
-const progressGuideInterstitialType = 'interstitialProgressGuide'
-const interstials: Record<
-  'following' | 'discover' | 'profile',
-  (FeedItem & {
-    type:
-      | 'interstitialFeeds'
-      | 'interstitialFollows'
-      | 'interstitialProgressGuide'
-  })[]
-> = {
-  following: [],
-  discover: [
-    {
-      type: progressGuideInterstitialType,
-      params: {
-        variant: 'default',
-      },
-      key: progressGuideInterstitialType,
-      slot: 0,
-    },
-    {
-      type: followInterstitialType,
-      params: {
-        variant: 'default',
-      },
-      key: followInterstitialType,
-      slot: 20,
-    },
-  ],
-  profile: [
-    {
-      type: followInterstitialType,
-      params: {
-        variant: 'default',
-      },
-      key: followInterstitialType,
-      slot: 5,
-    },
-  ],
-}
-
-export function getFeedPostSlice(feedItem: FeedItem): FeedPostSlice | null {
-  if (feedItem.type === 'slice') {
-    return feedItem.slice
+export function getFeedPostSlice(feedRow: FeedRow): FeedPostSlice | null {
+  if (feedRow.type === 'sliceItem') {
+    return feedRow.slice
   } else {
     return null
   }
@@ -204,7 +154,6 @@ let Feed = ({
   const checkForNewRef = React.useRef<(() => void) | null>(null)
   const lastFetchRef = React.useRef<number>(Date.now())
   const [feedType, feedUri, feedTab] = feed.split('|')
-  const gate = useGate()
 
   const opts = React.useMemo(
     () => ({enabled, ignoreFilterFor}),
@@ -303,8 +252,21 @@ let Feed = ({
     }
   }, [pollInterval])
 
-  const feedItems: FeedItem[] = React.useMemo(() => {
-    let arr: FeedItem[] = []
+  const feedItems: FeedRow[] = React.useMemo(() => {
+    let feedKind: 'following' | 'discover' | 'profile' | undefined
+    if (feedType === 'following') {
+      feedKind = 'following'
+    } else if (feedUri === DISCOVER_FEED_URI) {
+      feedKind = 'discover'
+    } else if (
+      feedType === 'author' &&
+      (feedTab === 'posts_and_author_threads' ||
+        feedTab === 'posts_with_replies')
+    ) {
+      feedKind = 'profile'
+    }
+
+    let arr: FeedRow[] = []
     if (KNOWN_SHUTDOWN_FEEDS.includes(feedUri)) {
       arr.push({
         type: 'feedShutdownMsg',
@@ -323,14 +285,77 @@ let Feed = ({
           key: 'empty',
         })
       } else if (data) {
+        let sliceIndex = -1
         for (const page of data?.pages) {
-          arr = arr.concat(
-            page.slices.map(s => ({
-              type: 'slice',
-              slice: s,
-              key: s._reactKey,
-            })),
-          )
+          for (const slice of page.slices) {
+            sliceIndex++
+
+            if (hasSession) {
+              if (feedKind === 'discover') {
+                if (sliceIndex === 0) {
+                  arr.push({
+                    type: 'interstitialProgressGuide',
+                    key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
+                  })
+                } else if (sliceIndex === 20) {
+                  arr.push({
+                    type: 'interstitialFollows',
+                    key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
+                  })
+                }
+              } else if (feedKind === 'profile') {
+                if (sliceIndex === 5) {
+                  arr.push({
+                    type: 'interstitialFollows',
+                    key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
+                  })
+                }
+              }
+            }
+
+            if (slice.isIncompleteThread && slice.items.length >= 3) {
+              const beforeLast = slice.items.length - 2
+              const last = slice.items.length - 1
+              arr.push({
+                type: 'sliceItem',
+                key: slice.items[0]._reactKey,
+                slice: slice,
+                indexInSlice: 0,
+                showReplyTo: false,
+              })
+              arr.push({
+                type: 'sliceViewFullThread',
+                key: slice._reactKey + '-viewFullThread',
+                uri: slice.items[0].uri,
+              })
+              arr.push({
+                type: 'sliceItem',
+                key: slice.items[beforeLast]._reactKey,
+                slice: slice,
+                indexInSlice: beforeLast,
+                showReplyTo:
+                  slice.items[beforeLast].parentAuthor?.did !==
+                  slice.items[beforeLast].post.author.did,
+              })
+              arr.push({
+                type: 'sliceItem',
+                key: slice.items[last]._reactKey,
+                slice: slice,
+                indexInSlice: last,
+                showReplyTo: false,
+              })
+            } else {
+              for (let i = 0; i < slice.items.length; i++) {
+                arr.push({
+                  type: 'sliceItem',
+                  key: slice.items[i]._reactKey,
+                  slice: slice,
+                  indexInSlice: i,
+                  showReplyTo: i === 0,
+                })
+              }
+            }
+          }
         }
       }
       if (isError && !isEmpty) {
@@ -346,45 +371,6 @@ let Feed = ({
       })
     }
 
-    if (hasSession) {
-      let feedKind: 'following' | 'discover' | 'profile' | undefined
-      if (feedType === 'following') {
-        feedKind = 'following'
-      } else if (feedUri === DISCOVER_FEED_URI) {
-        feedKind = 'discover'
-      } else if (
-        feedType === 'author' &&
-        (feedTab === 'posts_and_author_threads' ||
-          feedTab === 'posts_with_replies')
-      ) {
-        feedKind = 'profile'
-      }
-
-      if (feedKind) {
-        for (const interstitial of interstials[feedKind]) {
-          const shouldShow =
-            (interstitial.type === feedInterstitialType &&
-              gate('suggested_feeds_interstitial')) ||
-            interstitial.type === followInterstitialType ||
-            interstitial.type === progressGuideInterstitialType
-
-          if (shouldShow) {
-            const variant = 'default' // replace with experiment variant
-            const int = {
-              ...interstitial,
-              params: {variant},
-              // overwrite key with unique value
-              key: [interstitial.type, variant, lastFetchedAt].join(':'),
-            }
-
-            if (arr.length > interstitial.slot) {
-              arr.splice(interstitial.slot, 0, int)
-            }
-          }
-        }
-      }
-    }
-
     return arr
   }, [
     isFetched,
@@ -395,7 +381,6 @@ let Feed = ({
     feedType,
     feedUri,
     feedTab,
-    gate,
     hasSession,
   ])
 
@@ -403,7 +388,7 @@ let Feed = ({
   // =
 
   const onRefresh = React.useCallback(async () => {
-    logEvent('feed:refresh:sampled', {
+    logEvent('feed:refresh', {
       feedType: feedType,
       feedUrl: feed,
       reason: 'pull-to-refresh',
@@ -421,7 +406,7 @@ let Feed = ({
   const onEndReached = React.useCallback(async () => {
     if (isFetching || !hasNextPage || isError) return
 
-    logEvent('feed:endReached:sampled', {
+    logEvent('feed:endReached', {
       feedType: feedType,
       feedUrl: feed,
       itemCount: feedItems.length,
@@ -454,10 +439,10 @@ let Feed = ({
   // =
 
   const renderItem = React.useCallback(
-    ({item, index}: ListRenderItemInfo<FeedItem>) => {
-      if (item.type === 'empty') {
+    ({item: row, index: rowIndex}: ListRenderItemInfo<FeedRow>) => {
+      if (row.type === 'empty') {
         return renderEmptyState()
-      } else if (item.type === 'error') {
+      } else if (row.type === 'error') {
         return (
           <FeedErrorMessage
             feedDesc={feed}
@@ -466,7 +451,7 @@ let Feed = ({
             savedFeedConfig={savedFeedConfig}
           />
         )
-      } else if (item.type === 'loadMoreError') {
+      } else if (row.type === 'loadMoreError') {
         return (
           <LoadMoreRetryBtn
             label={_(
@@ -475,25 +460,48 @@ let Feed = ({
             onPress={onPressRetryLoadMore}
           />
         )
-      } else if (item.type === 'loading') {
+      } else if (row.type === 'loading') {
         return <PostFeedLoadingPlaceholder />
-      } else if (item.type === 'feedShutdownMsg') {
+      } else if (row.type === 'feedShutdownMsg') {
         return <FeedShutdownMsg feedUri={feedUri} />
-      } else if (item.type === feedInterstitialType) {
-        return <SuggestedFeeds />
-      } else if (item.type === followInterstitialType) {
+      } else if (row.type === 'interstitialFollows') {
         return <SuggestedFollows feed={feed} />
-      } else if (item.type === progressGuideInterstitialType) {
+      } else if (row.type === 'interstitialProgressGuide') {
         return <ProgressGuide />
-      } else if (item.type === 'slice') {
-        if (item.slice.isFallbackMarker) {
+      } else if (row.type === 'sliceItem') {
+        const slice = row.slice
+        if (slice.isFallbackMarker) {
           // HACK
           // tell the user we fell back to discover
           // see home.ts (feed api) for more info
           // -prf
           return <DiscoverFallbackHeader />
         }
-        return <FeedSlice slice={item.slice} hideTopBorder={index === 0} />
+        const indexInSlice = row.indexInSlice
+        const item = slice.items[indexInSlice]
+        return (
+          <FeedItem
+            post={item.post}
+            record={item.record}
+            reason={indexInSlice === 0 ? slice.reason : undefined}
+            feedContext={slice.feedContext}
+            moderation={item.moderation}
+            parentAuthor={item.parentAuthor}
+            showReplyTo={row.showReplyTo}
+            isThreadParent={isThreadParentAt(slice.items, indexInSlice)}
+            isThreadChild={isThreadChildAt(slice.items, indexInSlice)}
+            isThreadLastChild={
+              isThreadChildAt(slice.items, indexInSlice) &&
+              slice.items.length === indexInSlice + 1
+            }
+            isParentBlocked={item.isParentBlocked}
+            isParentNotFound={item.isParentNotFound}
+            hideTopBorder={rowIndex === 0 && indexInSlice === 0}
+            rootPost={slice.items[0].post}
+          />
+        )
+      } else if (row.type === 'sliceViewFullThread') {
+        return <ViewFullThread uri={row.uri} />
       } else {
         return null
       }
@@ -561,7 +569,7 @@ let Feed = ({
         }
         initialNumToRender={initialNumToRenderOverride ?? initialNumToRender}
         windowSize={9}
-        maxToRenderPerBatch={5}
+        maxToRenderPerBatch={isIOS ? 5 : 1}
         updateCellsBatchingPeriod={40}
         onItemSeen={feedFeedback.onItemSeen}
       />
@@ -574,3 +582,17 @@ export {Feed}
 const styles = StyleSheet.create({
   feedFooter: {paddingTop: 20},
 })
+
+function isThreadParentAt<T>(arr: Array<T>, i: number) {
+  if (arr.length === 1) {
+    return false
+  }
+  return i < arr.length - 1
+}
+
+function isThreadChildAt<T>(arr: Array<T>, i: number) {
+  if (arr.length === 1) {
+    return false
+  }
+  return i > 0
+}