about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2024-07-02 21:43:54 -0500
committerGitHub <noreply@github.com>2024-07-03 03:43:54 +0100
commit04cfd06639687012b59b52756015324cc622613b (patch)
treef4b4dda69d4e58b57d8e3bbcecff651f4f3df71f /src
parent0598fc2faa813486851f01451818220302f2f97a (diff)
downloadvoidsky-04cfd06639687012b59b52756015324cc622613b.tar.zst
[D1X] Integrate interstitials (#4698)
* Use discriminated union

* Integrate interstitials

* Add gates and handling for variants

* Only show interstitials for logged in accounts since flags are based on user ID

* Nit

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/FeedInterstitials.tsx8
-rw-r--r--src/lib/statsig/gates.ts2
-rw-r--r--src/view/com/posts/Feed.tsx223
3 files changed, 198 insertions, 35 deletions
diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx
index f1c4876a3..00342b39f 100644
--- a/src/components/FeedInterstitials.tsx
+++ b/src/components/FeedInterstitials.tsx
@@ -203,7 +203,7 @@ export function SuggestedFollows() {
             {content}
 
             <Button
-              label={_(msg`Browse more accounts on our explore page`)}
+              label={_(msg`Browse more accounts on the Explore page`)}
               onPress={() => {
                 navigation.navigate('SearchTab')
               }}>
@@ -211,7 +211,7 @@ export function SuggestedFollows() {
                 <View style={[a.flex_1, a.justify_center]}>
                   <View style={[a.flex_row, a.px_lg]}>
                     <Text style={[a.pr_xl, a.flex_1, a.leading_snug]}>
-                      <Trans>Browse more suggestions on our explore page</Trans>
+                      <Trans>Browse more suggestions on the Explore page</Trans>
                     </Text>
 
                     <Arrow size="xl" />
@@ -329,7 +329,7 @@ export function SuggestedFeeds() {
             {content}
 
             <Button
-              label={_(msg`Browse more feeds on our explore page`)}
+              label={_(msg`Browse more feeds on the Explore page`)}
               onPress={() => {
                 navigation.navigate('SearchTab')
               }}
@@ -338,7 +338,7 @@ export function SuggestedFeeds() {
                 <View style={[a.flex_1, a.justify_center]}>
                   <View style={[a.flex_row, a.px_lg]}>
                     <Text style={[a.pr_xl, a.flex_1, a.leading_snug]}>
-                      <Trans>Browse more suggestions on our explore page</Trans>
+                      <Trans>Browse more suggestions on the Explore page</Trans>
                     </Text>
 
                     <Arrow size="xl" />
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index b667245dd..0b253b278 100644
--- a/src/lib/statsig/gates.ts
+++ b/src/lib/statsig/gates.ts
@@ -6,3 +6,5 @@ export type Gate =
   | 'request_notifications_permission_after_onboarding_v2'
   | 'show_avi_follow_button'
   | 'show_follow_back_label_v2'
+  | 'suggested_feeds_interstitial'
+  | 'suggested_follows_interstitial'
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index 315286e72..3d90b8897 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -15,8 +15,8 @@ import {useLingui} from '@lingui/react'
 import {useQueryClient} from '@tanstack/react-query'
 
 import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home'
-import {KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants'
-import {logEvent} from '#/lib/statsig/statsig'
+import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants'
+import {logEvent, useGate} from '#/lib/statsig/statsig'
 import {logger} from '#/logger'
 import {isWeb} from '#/platform/detection'
 import {listenPostCreated} from '#/state/events'
@@ -25,6 +25,7 @@ import {STALE} from '#/state/queries'
 import {
   FeedDescriptor,
   FeedParams,
+  FeedPostSlice,
   pollLatest,
   RQKEY,
   usePostFeedQuery,
@@ -33,6 +34,7 @@ import {useSession} from '#/state/session'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
 import {useTheme} from 'lib/ThemeContext'
+import {SuggestedFeeds, SuggestedFollows} from '#/components/FeedInterstitials'
 import {List, ListRef} from '../util/List'
 import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
 import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
@@ -41,11 +43,92 @@ import {FeedErrorMessage} from './FeedErrorMessage'
 import {FeedShutdownMsg} from './FeedShutdownMsg'
 import {FeedSlice} from './FeedSlice'
 
-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__'}
-const FEED_SHUTDOWN_MSG_ITEM = {_reactKey: '__feed_shutdown_msg_item__'}
+type FeedItem =
+  | {
+      type: 'loading'
+      key: string
+    }
+  | {
+      type: 'empty'
+      key: string
+    }
+  | {
+      type: 'error'
+      key: string
+    }
+  | {
+      type: 'loadMoreError'
+      key: string
+    }
+  | {
+      type: 'feedShutdownMsg'
+      key: string
+    }
+  | {
+      type: 'slice'
+      key: string
+      slice: FeedPostSlice
+    }
+  | {
+      type: 'interstitialFeeds'
+      key: string
+      params: {
+        variant: 'default' | string
+      }
+      slot: number
+    }
+  | {
+      type: 'interstitialFollows'
+      key: string
+      params: {
+        variant: 'default' | string
+      }
+      slot: number
+    }
+
+const feedInterstitialType = 'interstitialFeeds'
+const followInterstitialType = 'interstitialFollows'
+const interstials: Record<
+  'following' | 'discover',
+  (FeedItem & {type: 'interstitialFeeds' | 'interstitialFollows'})[]
+> = {
+  following: [
+    {
+      type: followInterstitialType,
+      params: {
+        variant: 'default',
+      },
+      key: followInterstitialType,
+      slot: 20,
+    },
+    {
+      type: feedInterstitialType,
+      params: {
+        variant: 'default',
+      },
+      key: feedInterstitialType,
+      slot: 40,
+    },
+  ],
+  discover: [
+    {
+      type: feedInterstitialType,
+      params: {
+        variant: 'default',
+      },
+      key: feedInterstitialType,
+      slot: 20,
+    },
+    {
+      type: followInterstitialType,
+      params: {
+        variant: 'default',
+      },
+      key: followInterstitialType,
+      slot: 40,
+    },
+  ],
+}
 
 // DISABLED need to check if this is causing random feed refreshes -prf
 // const REFRESH_AFTER = STALE.HOURS.ONE
@@ -94,13 +177,16 @@ let Feed = ({
   const {track} = useAnalytics()
   const {_} = useLingui()
   const queryClient = useQueryClient()
-  const {currentAccount} = useSession()
+  const {currentAccount, hasSession} = useSession()
   const initialNumToRender = useInitialNumToRender()
   const feedFeedback = useFeedFeedbackContext()
   const [isPTRing, setIsPTRing] = React.useState(false)
   const checkForNewRef = React.useRef<(() => void) | null>(null)
   const lastFetchRef = React.useRef<number>(Date.now())
   const [feedType, feedUri] = feed.split('|')
+  const feedIsDiscover = feedUri === DISCOVER_FEED_URI
+  const feedIsFollowing = feedType === 'following'
+  const gate = useGate()
 
   const opts = React.useMemo(
     () => ({enabled, ignoreFilterFor}),
@@ -198,29 +284,94 @@ let Feed = ({
     }
   }, [pollInterval])
 
-  const feedItems = React.useMemo(() => {
-    let arr: any[] = []
+  const feedItems: FeedItem[] = React.useMemo(() => {
+    let arr: FeedItem[] = []
     if (KNOWN_SHUTDOWN_FEEDS.includes(feedUri)) {
-      arr = arr.concat([FEED_SHUTDOWN_MSG_ITEM])
+      arr.push({
+        type: 'feedShutdownMsg',
+        key: 'feedShutdownMsg',
+      })
     }
     if (isFetched) {
       if (isError && isEmpty) {
-        arr = arr.concat([ERROR_ITEM])
+        arr.push({
+          type: 'error',
+          key: 'error',
+        })
       } else if (isEmpty) {
-        arr = arr.concat([EMPTY_FEED_ITEM])
+        arr.push({
+          type: 'empty',
+          key: 'empty',
+        })
       } else if (data) {
         for (const page of data?.pages) {
-          arr = arr.concat(page.slices)
+          arr = arr.concat(
+            page.slices.map(s => ({
+              type: 'slice',
+              slice: s,
+              key: s._reactKey,
+            })),
+          )
         }
       }
       if (isError && !isEmpty) {
-        arr = arr.concat([LOAD_MORE_ERROR_ITEM])
+        arr.push({
+          type: 'loadMoreError',
+          key: 'loadMoreError',
+        })
       }
     } else {
-      arr.push(LOADING_ITEM)
+      arr.push({
+        type: 'loading',
+        key: 'loading',
+      })
     }
+
+    if (hasSession) {
+      const feedType = feedIsFollowing
+        ? 'following'
+        : feedIsDiscover
+        ? 'discover'
+        : undefined
+
+      if (feedType) {
+        for (const interstitial of interstials[feedType]) {
+          const feedInterstitialEnabled =
+            interstitial.type === feedInterstitialType &&
+            gate('suggested_feeds_interstitial')
+          const followInterstitialEnabled =
+            interstitial.type === followInterstitialType &&
+            gate('suggested_follows_interstitial')
+
+          if (feedInterstitialEnabled || followInterstitialEnabled) {
+            const variant = 'default' // replace with experiment variant
+            const int = {
+              ...interstitial,
+              params: {variant},
+              // overwrite key with unique value
+              key: [interstitial.type, variant].join(':'),
+            }
+
+            if (arr.length > interstitial.slot) {
+              arr.splice(interstitial.slot, 0, int)
+            }
+          }
+        }
+      }
+    }
+
     return arr
-  }, [isFetched, isError, isEmpty, data, feedUri])
+  }, [
+    isFetched,
+    isError,
+    isEmpty,
+    data,
+    feedUri,
+    feedIsDiscover,
+    feedIsFollowing,
+    gate,
+    hasSession,
+  ])
 
   // events
   // =
@@ -280,10 +431,10 @@ let Feed = ({
   // =
 
   const renderItem = React.useCallback(
-    ({item, index}: ListRenderItemInfo<any>) => {
-      if (item === EMPTY_FEED_ITEM) {
+    ({item, index}: ListRenderItemInfo<FeedItem>) => {
+      if (item.type === 'empty') {
         return renderEmptyState()
-      } else if (item === ERROR_ITEM) {
+      } else if (item.type === 'error') {
         return (
           <FeedErrorMessage
             feedDesc={feed}
@@ -292,7 +443,7 @@ let Feed = ({
             savedFeedConfig={savedFeedConfig}
           />
         )
-      } else if (item === LOAD_MORE_ERROR_ITEM) {
+      } else if (item.type === 'loadMoreError') {
         return (
           <LoadMoreRetryBtn
             label={_(
@@ -301,18 +452,28 @@ let Feed = ({
             onPress={onPressRetryLoadMore}
           />
         )
-      } else if (item === LOADING_ITEM) {
+      } else if (item.type === 'loading') {
         return <PostFeedLoadingPlaceholder />
-      } else if (item === FEED_SHUTDOWN_MSG_ITEM) {
+      } else if (item.type === 'feedShutdownMsg') {
         return <FeedShutdownMsg feedUri={feedUri} />
-      } else if (item.rootUri === FALLBACK_MARKER_POST.post.uri) {
-        // HACK
-        // tell the user we fell back to discover
-        // see home.ts (feed api) for more info
-        // -prf
-        return <DiscoverFallbackHeader />
+      } else if (item.type === feedInterstitialType) {
+        return <SuggestedFeeds />
+      } else if (item.type === followInterstitialType) {
+        return <SuggestedFollows />
+      } else if (item.type === 'slice') {
+        if (item.slice.rootUri === FALLBACK_MARKER_POST.post.uri) {
+          // 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 && !isWeb} />
+        )
+      } else {
+        return null
       }
-      return <FeedSlice slice={item} hideTopBorder={index === 0 && !isWeb} />
     },
     [
       renderEmptyState,
@@ -354,7 +515,7 @@ let Feed = ({
         testID={testID ? `${testID}-flatlist` : undefined}
         ref={scrollElRef}
         data={feedItems}
-        keyExtractor={item => item._reactKey}
+        keyExtractor={item => item.key}
         renderItem={renderItem}
         ListFooterComponent={FeedFooter}
         ListHeaderComponent={ListHeaderComponent}