about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-10-04 08:57:23 -0700
committerGitHub <noreply@github.com>2023-10-04 08:57:23 -0700
commitb1a1bae02e021e509f678ba423a4d030166a02a9 (patch)
tree4143d3befce048701229111c6203e9493c225b73 /src
parenta76fb78d532e436b6b84efd09d70088410a2bb20 (diff)
downloadvoidsky-b1a1bae02e021e509f678ba423a4d030166a02a9.tar.zst
Onboarding & feed fixes (#1602)
* Fix: improve the 'end of feed' detection condition

* Fix the feeds link on mobile in the empty state

* Align the following empty state better on web

* Dont autofocus the search input in the search tab

* Fix the error boundary render

* Add 'end of feed' CTA to following feed

* Reduce the default feeds to discover now that we have feed-selection during onboarding

* Fix case where loading spinner fails to stop rendering in bottom of feed

* Fix: dont show loading spinner at footer of feed when refreshing

* Fix: dont fire reminders during onboarding

* Optimize adding feeds and update to mirror the api behaviors more closely

* Use the lock in preferences to avoid clobbering in-flight updates

* Refresh the feed after onboarding to ensure content is visible

* Remove the now-incorrect comment

* Tune copy
Diffstat (limited to 'src')
-rw-r--r--src/lib/constants.ts13
-rw-r--r--src/state/models/discovery/onboarding.ts1
-rw-r--r--src/state/models/feeds/posts.ts6
-rw-r--r--src/state/models/ui/preferences.ts111
-rw-r--r--src/state/models/ui/reminders.ts7
-rw-r--r--src/view/com/auth/onboarding/RecommendedFeedsItem.tsx1
-rw-r--r--src/view/com/posts/Feed.tsx8
-rw-r--r--src/view/com/posts/FollowingEmptyState.tsx98
-rw-r--r--src/view/com/posts/FollowingEndOfFeed.tsx100
-rw-r--r--src/view/com/search/HeaderWithInput.tsx2
-rw-r--r--src/view/com/util/ErrorBoundary.tsx2
-rw-r--r--src/view/screens/Home.tsx9
12 files changed, 262 insertions, 96 deletions
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 1a7949e6a..81a6d4e77 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -79,6 +79,7 @@ export async function DEFAULT_FEEDS(
   serviceUrl: string,
   resolveHandle: (name: string) => Promise<string>,
 ) {
+  // TODO: remove this when the test suite no longer relies on it
   if (IS_LOCAL_DEV(serviceUrl)) {
     // local dev
     const aliceDid = await resolveHandle('alice.test')
@@ -106,16 +107,8 @@ export async function DEFAULT_FEEDS(
   } else {
     // production
     return {
-      pinned: [
-        PROD_DEFAULT_FEED('whats-hot'),
-        PROD_DEFAULT_FEED('with-friends'),
-      ],
-      saved: [
-        PROD_DEFAULT_FEED('bsky-team'),
-        PROD_DEFAULT_FEED('with-friends'),
-        PROD_DEFAULT_FEED('whats-hot'),
-        PROD_DEFAULT_FEED('hot-classic'),
-      ],
+      pinned: [PROD_DEFAULT_FEED('whats-hot')],
+      saved: [PROD_DEFAULT_FEED('whats-hot')],
     }
   }
 }
diff --git a/src/state/models/discovery/onboarding.ts b/src/state/models/discovery/onboarding.ts
index 8ad321ed9..3638e7f0d 100644
--- a/src/state/models/discovery/onboarding.ts
+++ b/src/state/models/discovery/onboarding.ts
@@ -81,6 +81,7 @@ export class OnboardingModel {
   }
 
   finish() {
+    this.rootStore.me.mainFeed.refresh() // load the selected content
     this.step = 'Home'
     track('Onboarding:Complete')
   }
diff --git a/src/state/models/feeds/posts.ts b/src/state/models/feeds/posts.ts
index bb619147f..2a7170325 100644
--- a/src/state/models/feeds/posts.ts
+++ b/src/state/models/feeds/posts.ts
@@ -116,6 +116,10 @@ export class PostsFeedModel {
     return this.hasLoaded && !this.hasContent
   }
 
+  get isLoadingMore() {
+    return this.isLoading && !this.isRefreshing
+  }
+
   setHasNewLatest(v: boolean) {
     this.hasNewLatest = v
   }
@@ -307,7 +311,7 @@ export class PostsFeedModel {
   }
 
   async _appendAll(res: FeedAPIResponse, replace = false) {
-    this.hasMore = !!res.cursor
+    this.hasMore = !!res.cursor && res.feed.length > 0
     if (replace) {
       this.emptyFetches = 0
     }
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts
index b3365bd7c..6ca19b4b7 100644
--- a/src/state/models/ui/preferences.ts
+++ b/src/state/models/ui/preferences.ts
@@ -418,6 +418,7 @@ export class PreferencesModel {
     const oldPinned = this.pinnedFeeds
     this.savedFeeds = saved
     this.pinnedFeeds = pinned
+    await this.lock.acquireAsync()
     try {
       const res = await cb()
       runInAction(() => {
@@ -430,6 +431,8 @@ export class PreferencesModel {
         this.pinnedFeeds = oldPinned
       })
       throw e
+    } finally {
+      this.lock.release()
     }
   }
 
@@ -441,7 +444,7 @@ export class PreferencesModel {
 
   async addSavedFeed(v: string) {
     return this._optimisticUpdateSavedFeeds(
-      [...this.savedFeeds, v],
+      [...this.savedFeeds.filter(uri => uri !== v), v],
       this.pinnedFeeds,
       () => this.rootStore.agent.addSavedFeed(v),
     )
@@ -457,8 +460,8 @@ export class PreferencesModel {
 
   async addPinnedFeed(v: string) {
     return this._optimisticUpdateSavedFeeds(
-      this.savedFeeds,
-      [...this.pinnedFeeds, v],
+      [...this.savedFeeds.filter(uri => uri !== v), v],
+      [...this.pinnedFeeds.filter(uri => uri !== v), v],
       () => this.rootStore.agent.addPinnedFeed(v),
     )
   }
@@ -473,71 +476,121 @@ export class PreferencesModel {
 
   async setBirthDate(birthDate: Date) {
     this.birthDate = birthDate
-    await this.rootStore.agent.setPersonalDetails({birthDate})
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setPersonalDetails({birthDate})
+    } finally {
+      this.lock.release()
+    }
   }
 
   async toggleHomeFeedHideReplies() {
     this.homeFeed.hideReplies = !this.homeFeed.hideReplies
-    await this.rootStore.agent.setFeedViewPrefs('home', {
-      hideReplies: this.homeFeed.hideReplies,
-    })
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setFeedViewPrefs('home', {
+        hideReplies: this.homeFeed.hideReplies,
+      })
+    } finally {
+      this.lock.release()
+    }
   }
 
   async toggleHomeFeedHideRepliesByUnfollowed() {
     this.homeFeed.hideRepliesByUnfollowed =
       !this.homeFeed.hideRepliesByUnfollowed
-    await this.rootStore.agent.setFeedViewPrefs('home', {
-      hideRepliesByUnfollowed: this.homeFeed.hideRepliesByUnfollowed,
-    })
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setFeedViewPrefs('home', {
+        hideRepliesByUnfollowed: this.homeFeed.hideRepliesByUnfollowed,
+      })
+    } finally {
+      this.lock.release()
+    }
   }
 
   async setHomeFeedHideRepliesByLikeCount(threshold: number) {
     this.homeFeed.hideRepliesByLikeCount = threshold
-    await this.rootStore.agent.setFeedViewPrefs('home', {
-      hideRepliesByLikeCount: this.homeFeed.hideRepliesByLikeCount,
-    })
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setFeedViewPrefs('home', {
+        hideRepliesByLikeCount: this.homeFeed.hideRepliesByLikeCount,
+      })
+    } finally {
+      this.lock.release()
+    }
   }
 
   async toggleHomeFeedHideReposts() {
     this.homeFeed.hideReposts = !this.homeFeed.hideReposts
-    await this.rootStore.agent.setFeedViewPrefs('home', {
-      hideReposts: this.homeFeed.hideReposts,
-    })
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setFeedViewPrefs('home', {
+        hideReposts: this.homeFeed.hideReposts,
+      })
+    } finally {
+      this.lock.release()
+    }
   }
 
   async toggleHomeFeedHideQuotePosts() {
     this.homeFeed.hideQuotePosts = !this.homeFeed.hideQuotePosts
-    await this.rootStore.agent.setFeedViewPrefs('home', {
-      hideQuotePosts: this.homeFeed.hideQuotePosts,
-    })
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setFeedViewPrefs('home', {
+        hideQuotePosts: this.homeFeed.hideQuotePosts,
+      })
+    } finally {
+      this.lock.release()
+    }
   }
 
   async toggleHomeFeedMergeFeedEnabled() {
     this.homeFeed.lab_mergeFeedEnabled = !this.homeFeed.lab_mergeFeedEnabled
-    await this.rootStore.agent.setFeedViewPrefs('home', {
-      lab_mergeFeedEnabled: this.homeFeed.lab_mergeFeedEnabled,
-    })
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setFeedViewPrefs('home', {
+        lab_mergeFeedEnabled: this.homeFeed.lab_mergeFeedEnabled,
+      })
+    } finally {
+      this.lock.release()
+    }
   }
 
   async setThreadSort(v: string) {
     if (THREAD_SORT_VALUES.includes(v)) {
       this.thread.sort = v
-      await this.rootStore.agent.setThreadViewPrefs({sort: v})
+      await this.lock.acquireAsync()
+      try {
+        await this.rootStore.agent.setThreadViewPrefs({sort: v})
+      } finally {
+        this.lock.release()
+      }
     }
   }
 
   async togglePrioritizedFollowedUsers() {
     this.thread.prioritizeFollowedUsers = !this.thread.prioritizeFollowedUsers
-    await this.rootStore.agent.setThreadViewPrefs({
-      prioritizeFollowedUsers: this.thread.prioritizeFollowedUsers,
-    })
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setThreadViewPrefs({
+        prioritizeFollowedUsers: this.thread.prioritizeFollowedUsers,
+      })
+    } finally {
+      this.lock.release()
+    }
   }
 
   async toggleThreadTreeViewEnabled() {
     this.thread.lab_treeViewEnabled = !this.thread.lab_treeViewEnabled
-    await this.rootStore.agent.setThreadViewPrefs({
-      lab_treeViewEnabled: this.thread.lab_treeViewEnabled,
-    })
+    await this.lock.acquireAsync()
+    try {
+      await this.rootStore.agent.setThreadViewPrefs({
+        lab_treeViewEnabled: this.thread.lab_treeViewEnabled,
+      })
+    } finally {
+      this.lock.release()
+    }
   }
 
   toggleRequireAltTextEnabled() {
diff --git a/src/state/models/ui/reminders.ts b/src/state/models/ui/reminders.ts
index f8becdec3..60dbf5d88 100644
--- a/src/state/models/ui/reminders.ts
+++ b/src/state/models/ui/reminders.ts
@@ -6,10 +6,6 @@ import {toHashCode} from 'lib/strings/helpers'
 const DAY = 60e3 * 24 * 1 // 1 day (ms)
 
 export class Reminders {
-  // NOTE
-  // by defaulting to the current date, we ensure that the user won't be nagged
-  // on first run (aka right after creating an account)
-  // -prf
   lastEmailConfirm: Date = new Date()
 
   constructor(public rootStore: RootStoreModel) {
@@ -46,6 +42,9 @@ export class Reminders {
     if (sess.emailConfirmed) {
       return false
     }
+    if (this.rootStore.onboarding.isActive) {
+      return false
+    }
     const today = new Date()
     // shard the users into 2 day of the week buckets
     // (this is to avoid a sudden influx of email updates when
diff --git a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx
index d130dc138..6796c64db 100644
--- a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx
+++ b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx
@@ -30,7 +30,6 @@ export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({
       }
     } else {
       try {
-        await item.save()
         await item.pin()
       } catch (e) {
         Toast.show('There was an issue contacting your server')
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index 55e69a318..b095fe07b 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -33,6 +33,7 @@ export const Feed = observer(function Feed({
   onScroll,
   scrollEventThrottle,
   renderEmptyState,
+  renderEndOfFeed,
   testID,
   headerOffset = 0,
   ListHeaderComponent,
@@ -45,6 +46,7 @@ export const Feed = observer(function Feed({
   onScroll?: OnScrollCb
   scrollEventThrottle?: number
   renderEmptyState?: () => JSX.Element
+  renderEndOfFeed?: () => JSX.Element
   testID?: string
   headerOffset?: number
   ListHeaderComponent?: () => JSX.Element
@@ -142,14 +144,16 @@ export const Feed = observer(function Feed({
 
   const FeedFooter = React.useCallback(
     () =>
-      feed.isLoading ? (
+      feed.isLoadingMore ? (
         <View style={styles.feedFooter}>
           <ActivityIndicator />
         </View>
+      ) : !feed.hasMore && !feed.isEmpty && renderEndOfFeed ? (
+        renderEndOfFeed()
       ) : (
         <View />
       ),
-    [feed],
+    [feed.isLoadingMore, feed.hasMore, feed.isEmpty, renderEndOfFeed],
   )
 
   return (
diff --git a/src/view/com/posts/FollowingEmptyState.tsx b/src/view/com/posts/FollowingEmptyState.tsx
index a73ffb68b..61a27e48e 100644
--- a/src/view/com/posts/FollowingEmptyState.tsx
+++ b/src/view/com/posts/FollowingEmptyState.tsx
@@ -28,60 +28,73 @@ export function FollowingEmptyState() {
   }, [navigation])
 
   const onPressDiscoverFeeds = React.useCallback(() => {
-    navigation.navigate('Feeds')
+    if (isWeb) {
+      navigation.navigate('Feeds')
+    } else {
+      navigation.navigate('FeedsTab')
+      navigation.popToTop()
+    }
   }, [navigation])
 
   return (
-    <View style={styles.emptyContainer}>
-      <View style={styles.emptyIconContainer}>
-        <MagnifyingGlassIcon style={[styles.emptyIcon, pal.text]} size={62} />
-      </View>
-      <Text type="xl-medium" style={[s.textCenter, pal.text]}>
-        Your following feed is empty! Find some accounts to follow to fix this.
-      </Text>
-      <Button
-        type="inverted"
-        style={styles.emptyBtn}
-        onPress={onPressFindAccounts}>
-        <Text type="lg-medium" style={palInverted.text}>
-          Find accounts to follow
+    <View style={styles.container}>
+      <View style={styles.inner}>
+        <View style={styles.iconContainer}>
+          <MagnifyingGlassIcon style={[styles.icon, pal.text]} size={62} />
+        </View>
+        <Text type="xl-medium" style={[s.textCenter, pal.text]}>
+          Your following feed is empty! Follow more users to see what's
+          happening.
         </Text>
-        <FontAwesomeIcon
-          icon="angle-right"
-          style={palInverted.text as FontAwesomeIconStyle}
-          size={14}
-        />
-      </Button>
+        <Button
+          type="inverted"
+          style={styles.emptyBtn}
+          onPress={onPressFindAccounts}>
+          <Text type="lg-medium" style={palInverted.text}>
+            Find accounts to follow
+          </Text>
+          <FontAwesomeIcon
+            icon="angle-right"
+            style={palInverted.text as FontAwesomeIconStyle}
+            size={14}
+          />
+        </Button>
 
-      <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
-        You can also discover new Custom Feeds to follow.
-      </Text>
-      <Button
-        type="inverted"
-        style={[styles.emptyBtn, s.mt10]}
-        onPress={onPressDiscoverFeeds}>
-        <Text type="lg-medium" style={palInverted.text}>
-          Discover new custom feeds
+        <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
+          You can also discover new Custom Feeds to follow.
         </Text>
-        <FontAwesomeIcon
-          icon="angle-right"
-          style={palInverted.text as FontAwesomeIconStyle}
-          size={14}
-        />
-      </Button>
+        <Button
+          type="inverted"
+          style={[styles.emptyBtn, s.mt10]}
+          onPress={onPressDiscoverFeeds}>
+          <Text type="lg-medium" style={palInverted.text}>
+            Discover new custom feeds
+          </Text>
+          <FontAwesomeIcon
+            icon="angle-right"
+            style={palInverted.text as FontAwesomeIconStyle}
+            size={14}
+          />
+        </Button>
+      </View>
     </View>
   )
 }
 const styles = StyleSheet.create({
-  emptyContainer: {
+  container: {
     height: '100%',
+    flexDirection: 'row',
+    justifyContent: 'center',
     paddingVertical: 40,
     paddingHorizontal: 30,
   },
-  emptyIconContainer: {
+  inner: {
+    maxWidth: 460,
+  },
+  iconContainer: {
     marginBottom: 16,
   },
-  emptyIcon: {
+  icon: {
     marginLeft: 'auto',
     marginRight: 'auto',
   },
@@ -94,13 +107,4 @@ const styles = StyleSheet.create({
     paddingHorizontal: 24,
     borderRadius: 30,
   },
-
-  feedsTip: {
-    position: 'absolute',
-    left: 22,
-  },
-  feedsTipArrow: {
-    marginLeft: 32,
-    marginTop: 8,
-  },
 })
diff --git a/src/view/com/posts/FollowingEndOfFeed.tsx b/src/view/com/posts/FollowingEndOfFeed.tsx
new file mode 100644
index 000000000..48724d8b3
--- /dev/null
+++ b/src/view/com/posts/FollowingEndOfFeed.tsx
@@ -0,0 +1,100 @@
+import React from 'react'
+import {StyleSheet, View} from 'react-native'
+import {useNavigation} from '@react-navigation/native'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {Text} from '../util/text/Text'
+import {Button} from '../util/forms/Button'
+import {NavigationProp} from 'lib/routes/types'
+import {usePalette} from 'lib/hooks/usePalette'
+import {s} from 'lib/styles'
+import {isWeb} from 'platform/detection'
+
+export function FollowingEndOfFeed() {
+  const pal = usePalette('default')
+  const palInverted = usePalette('inverted')
+  const navigation = useNavigation<NavigationProp>()
+
+  const onPressFindAccounts = React.useCallback(() => {
+    if (isWeb) {
+      navigation.navigate('Search', {})
+    } else {
+      navigation.navigate('SearchTab')
+      navigation.popToTop()
+    }
+  }, [navigation])
+
+  const onPressDiscoverFeeds = React.useCallback(() => {
+    if (isWeb) {
+      navigation.navigate('Feeds')
+    } else {
+      navigation.navigate('FeedsTab')
+      navigation.popToTop()
+    }
+  }, [navigation])
+
+  return (
+    <View style={[styles.container, pal.border]}>
+      <View style={styles.inner}>
+        <Text type="xl-medium" style={[s.textCenter, pal.text]}>
+          You've reached the end of your feed! Find some more accounts to
+          follow.
+        </Text>
+        <Button
+          type="inverted"
+          style={styles.emptyBtn}
+          onPress={onPressFindAccounts}>
+          <Text type="lg-medium" style={palInverted.text}>
+            Find accounts to follow
+          </Text>
+          <FontAwesomeIcon
+            icon="angle-right"
+            style={palInverted.text as FontAwesomeIconStyle}
+            size={14}
+          />
+        </Button>
+
+        <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
+          You can also discover new Custom Feeds to follow.
+        </Text>
+        <Button
+          type="inverted"
+          style={[styles.emptyBtn, s.mt10]}
+          onPress={onPressDiscoverFeeds}>
+          <Text type="lg-medium" style={palInverted.text}>
+            Discover new custom feeds
+          </Text>
+          <FontAwesomeIcon
+            icon="angle-right"
+            style={palInverted.text as FontAwesomeIconStyle}
+            size={14}
+          />
+        </Button>
+      </View>
+    </View>
+  )
+}
+const styles = StyleSheet.create({
+  container: {
+    flexDirection: 'row',
+    justifyContent: 'center',
+    paddingTop: 40,
+    paddingBottom: 80,
+    paddingHorizontal: 30,
+    borderTopWidth: 1,
+  },
+  inner: {
+    maxWidth: 460,
+  },
+  emptyBtn: {
+    marginVertical: 20,
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    paddingVertical: 18,
+    paddingHorizontal: 24,
+    borderRadius: 30,
+  },
+})
diff --git a/src/view/com/search/HeaderWithInput.tsx b/src/view/com/search/HeaderWithInput.tsx
index f04175afd..6bd1b2f00 100644
--- a/src/view/com/search/HeaderWithInput.tsx
+++ b/src/view/com/search/HeaderWithInput.tsx
@@ -93,7 +93,7 @@ export function HeaderWithInput({
           onBlur={() => setIsInputFocused(false)}
           onChangeText={onChangeQuery}
           onSubmitEditing={onSubmitQuery}
-          autoFocus={isMobile}
+          autoFocus={false}
           accessibilityRole="search"
           accessibilityLabel="Search"
           accessibilityHint=""
diff --git a/src/view/com/util/ErrorBoundary.tsx b/src/view/com/util/ErrorBoundary.tsx
index c7374e195..529435cf1 100644
--- a/src/view/com/util/ErrorBoundary.tsx
+++ b/src/view/com/util/ErrorBoundary.tsx
@@ -28,7 +28,7 @@ export class ErrorBoundary extends Component<Props, State> {
   public render() {
     if (this.state.hasError) {
       return (
-        <CenteredView>
+        <CenteredView style={{height: '100%', flex: 1}}>
           <ErrorScreen
             title="Oh no!"
             message="There was an unexpected issue in the application. Please let us know if this happened to you!"
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index e53d4a08e..8560ad445 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -13,6 +13,7 @@ import {withAuthRequired} from 'view/com/auth/withAuthRequired'
 import {TextLink} from 'view/com/util/Link'
 import {Feed} from '../com/posts/Feed'
 import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
+import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
 import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
 import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn'
 import {FeedsTabBar} from '../com/pager/FeedsTabBar'
@@ -110,6 +111,10 @@ export const HomeScreen = withAuthRequired(
       return <FollowingEmptyState />
     }, [])
 
+    const renderFollowingEndOfFeed = React.useCallback(() => {
+      return <FollowingEndOfFeed />
+    }, [])
+
     const renderCustomFeedEmptyState = React.useCallback(() => {
       return <CustomFeedEmptyState />
     }, [])
@@ -127,6 +132,7 @@ export const HomeScreen = withAuthRequired(
           isPageFocused={selectedPage === 0}
           feed={store.me.mainFeed}
           renderEmptyState={renderFollowingEmptyState}
+          renderEndOfFeed={renderFollowingEndOfFeed}
         />
         {customFeeds.map((f, index) => {
           return (
@@ -149,11 +155,13 @@ const FeedPage = observer(function FeedPageImpl({
   isPageFocused,
   feed,
   renderEmptyState,
+  renderEndOfFeed,
 }: {
   testID?: string
   feed: PostsFeedModel
   isPageFocused: boolean
   renderEmptyState?: () => JSX.Element
+  renderEndOfFeed?: () => JSX.Element
 }) {
   const store = useStores()
   const pal = usePalette('default')
@@ -307,6 +315,7 @@ const FeedPage = observer(function FeedPageImpl({
         onScroll={onMainScroll}
         scrollEventThrottle={100}
         renderEmptyState={renderEmptyState}
+        renderEndOfFeed={renderEndOfFeed}
         ListHeaderComponent={ListHeaderComponent}
         headerOffset={headerOffset}
       />