about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/hooks/useDesktopRightNavItems.ts51
-rw-r--r--src/lib/hooks/useHomeTabs.ts29
-rw-r--r--src/state/models/content/feed-source.ts2
-rw-r--r--src/state/models/ui/preferences.ts18
-rw-r--r--src/state/queries/feed.ts132
-rw-r--r--src/state/queries/preferences/types.ts5
-rw-r--r--src/view/com/modals/ContentFilteringSettings.tsx5
-rw-r--r--src/view/com/pager/FeedsTabBar.web.tsx7
-rw-r--r--src/view/com/pager/FeedsTabBarMobile.tsx7
-rw-r--r--src/view/screens/Feeds.tsx2
-rw-r--r--src/view/screens/Home.tsx43
-rw-r--r--src/view/shell/desktop/Feeds.tsx52
12 files changed, 177 insertions, 176 deletions
diff --git a/src/lib/hooks/useDesktopRightNavItems.ts b/src/lib/hooks/useDesktopRightNavItems.ts
deleted file mode 100644
index f27efd28f..000000000
--- a/src/lib/hooks/useDesktopRightNavItems.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import {useEffect, useState} from 'react'
-import {useStores} from 'state/index'
-import isEqual from 'lodash.isequal'
-import {AtUri} from '@atproto/api'
-import {FeedSourceModel} from 'state/models/content/feed-source'
-
-interface RightNavItem {
-  uri: string
-  href: string
-  hostname: string
-  collection: string
-  rkey: string
-  displayName: string
-}
-
-export function useDesktopRightNavItems(uris: string[]): RightNavItem[] {
-  const store = useStores()
-  const [items, setItems] = useState<RightNavItem[]>([])
-  const [lastUris, setLastUris] = useState<string[]>([])
-
-  useEffect(() => {
-    if (isEqual(uris, lastUris)) {
-      // no changes
-      return
-    }
-
-    async function fetchFeedInfo() {
-      const models = uris
-        .slice(0, 25)
-        .map(uri => new FeedSourceModel(store, uri))
-      await Promise.all(models.map(m => m.setup()))
-      setItems(
-        models.map(model => {
-          const {hostname, collection, rkey} = new AtUri(model.uri)
-          return {
-            uri: model.uri,
-            href: model.href,
-            hostname,
-            collection,
-            rkey,
-            displayName: model.displayName,
-          }
-        }),
-      )
-      setLastUris(uris)
-    }
-    fetchFeedInfo()
-  }, [store, uris, lastUris, setLastUris, setItems])
-
-  return items
-}
diff --git a/src/lib/hooks/useHomeTabs.ts b/src/lib/hooks/useHomeTabs.ts
deleted file mode 100644
index 69183e627..000000000
--- a/src/lib/hooks/useHomeTabs.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import {useEffect, useState} from 'react'
-import {useStores} from 'state/index'
-import isEqual from 'lodash.isequal'
-import {FeedSourceModel} from 'state/models/content/feed-source'
-
-export function useHomeTabs(uris: string[]): string[] {
-  const store = useStores()
-  const [tabs, setTabs] = useState<string[]>(['Following'])
-  const [lastUris, setLastUris] = useState<string[]>([])
-
-  useEffect(() => {
-    if (isEqual(uris, lastUris)) {
-      // no changes
-      return
-    }
-
-    async function fetchFeedInfo() {
-      const models = uris
-        .slice(0, 25)
-        .map(uri => new FeedSourceModel(store, uri))
-      await Promise.all(models.map(m => m.setup()))
-      setTabs(['Following'].concat(models.map(f => f.displayName)))
-      setLastUris(uris)
-    }
-    fetchFeedInfo()
-  }, [store, uris, lastUris, setLastUris, setTabs])
-
-  return tabs
-}
diff --git a/src/state/models/content/feed-source.ts b/src/state/models/content/feed-source.ts
index 156e3be3b..cd8c08b56 100644
--- a/src/state/models/content/feed-source.ts
+++ b/src/state/models/content/feed-source.ts
@@ -61,7 +61,7 @@ export class FeedSourceModel {
   }
 
   get isPinned() {
-    return this.rootStore.preferences.isPinnedFeed(this.uri)
+    return false
   }
 
   get isLiked() {
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts
index 4f43487e7..1068ac651 100644
--- a/src/state/models/ui/preferences.ts
+++ b/src/state/models/ui/preferences.ts
@@ -4,7 +4,6 @@ import {
   BskyFeedViewPreference,
   BskyThreadViewPreference,
 } from '@atproto/api'
-import AwaitLock from 'await-lock'
 import {isObj, hasProp} from 'lib/type-guards'
 import {RootStoreModel} from '../root-store'
 import {ModerationOpts} from '@atproto/api'
@@ -33,30 +32,17 @@ export class LabelPreferencesModel {
 }
 
 export class PreferencesModel {
-  adultContentEnabled = false
   contentLabels = new LabelPreferencesModel()
   savedFeeds: string[] = []
   pinnedFeeds: string[] = []
-  birthDate: Date | undefined = undefined
-  homeFeed: FeedViewPreference = {
-    hideReplies: false,
-    hideRepliesByUnfollowed: false,
-    hideRepliesByLikeCount: 0,
-    hideReposts: false,
-    hideQuotePosts: false,
-    lab_mergeFeedEnabled: false, // experimental
-  }
   thread: ThreadViewPreference = {
     sort: 'oldest',
     prioritizeFollowedUsers: true,
     lab_treeViewEnabled: false, // experimental
   }
 
-  // used to linearize async modifications to state
-  lock = new AwaitLock()
-
   constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this, {lock: false}, {autoBind: true})
+    makeAutoObservable(this, {}, {autoBind: true})
   }
 
   serialize() {
@@ -106,7 +92,7 @@ export class PreferencesModel {
   get moderationOpts(): ModerationOpts {
     return {
       userDid: this.rootStore.session.currentSession?.did || '',
-      adultContentEnabled: this.adultContentEnabled,
+      adultContentEnabled: false,
       labels: {
         // TEMP translate old settings until this UI can be migrated -prf
         porn: tempfixLabelPref(this.contentLabels.nsfw),
diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts
index 5754d2c70..dde37315d 100644
--- a/src/state/queries/feed.ts
+++ b/src/state/queries/feed.ts
@@ -1,9 +1,11 @@
+import React from 'react'
 import {
   useQuery,
   useInfiniteQuery,
   InfiniteData,
   QueryKey,
   useMutation,
+  useQueryClient,
 } from '@tanstack/react-query'
 import {
   AtUri,
@@ -13,16 +15,22 @@ import {
   AppBskyUnspeccedGetPopularFeedGenerators,
 } from '@atproto/api'
 
+import {router} from '#/routes'
 import {sanitizeDisplayName} from '#/lib/strings/display-names'
 import {sanitizeHandle} from '#/lib/strings/handles'
 import {useSession} from '#/state/session'
+import {usePreferencesQuery} from '#/state/queries/preferences'
 
-type FeedSourceInfo =
+export type FeedSourceInfo =
   | {
       type: 'feed'
       uri: string
+      route: {
+        href: string
+        name: string
+        params: Record<string, string>
+      }
       cid: string
-      href: string
       avatar: string | undefined
       displayName: string
       description: RichText
@@ -34,8 +42,12 @@ type FeedSourceInfo =
   | {
       type: 'list'
       uri: string
+      route: {
+        href: string
+        name: string
+        params: Record<string, string>
+      }
       cid: string
-      href: string
       avatar: string | undefined
       displayName: string
       description: RichText
@@ -43,7 +55,7 @@ type FeedSourceInfo =
       creatorHandle: string
     }
 
-export const useFeedSourceInfoQueryKey = ({uri}: {uri: string}) => [
+export const feedSourceInfoQueryKey = ({uri}: {uri: string}) => [
   'getFeedSourceInfo',
   uri,
 ]
@@ -53,19 +65,24 @@ const feedSourceNSIDs = {
   list: 'app.bsky.graph.list',
 }
 
-function hydrateFeedGenerator(
+export function hydrateFeedGenerator(
   view: AppBskyFeedDefs.GeneratorView,
 ): FeedSourceInfo {
   const urip = new AtUri(view.uri)
   const collection =
     urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'lists'
   const href = `/profile/${urip.hostname}/${collection}/${urip.rkey}`
+  const route = router.matchPath(href)
 
   return {
     type: 'feed',
     uri: view.uri,
     cid: view.cid,
-    href,
+    route: {
+      href,
+      name: route[0],
+      params: route[1],
+    },
     avatar: view.avatar,
     displayName: view.displayName
       ? sanitizeDisplayName(view.displayName)
@@ -81,17 +98,22 @@ function hydrateFeedGenerator(
   }
 }
 
-function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
+export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
   const urip = new AtUri(view.uri)
   const collection =
     urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'lists'
   const href = `/profile/${urip.hostname}/${collection}/${urip.rkey}`
+  const route = router.matchPath(href)
 
   return {
     type: 'list',
     uri: view.uri,
+    route: {
+      href,
+      name: route[0],
+      params: route[1],
+    },
     cid: view.cid,
-    href,
     avatar: view.avatar,
     description: new RichText({
       text: view.description || '',
@@ -105,13 +127,17 @@ function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
   }
 }
 
+export function getFeedTypeFromUri(uri: string) {
+  const {pathname} = new AtUri(uri)
+  return pathname.includes(feedSourceNSIDs.feed) ? 'feed' : 'list'
+}
+
 export function useFeedSourceInfoQuery({uri}: {uri: string}) {
   const {agent} = useSession()
-  const {pathname} = new AtUri(uri)
-  const type = pathname.includes(feedSourceNSIDs.feed) ? 'feed' : 'list'
+  const type = getFeedTypeFromUri(uri)
 
   return useQuery({
-    queryKey: useFeedSourceInfoQueryKey({uri}),
+    queryKey: feedSourceInfoQueryKey({uri}),
     queryFn: async () => {
       let view: FeedSourceInfo
 
@@ -170,3 +196,87 @@ export function useSearchPopularFeedsMutation() {
     },
   })
 }
+
+const FOLLOWING_FEED_STUB: FeedSourceInfo = {
+  type: 'feed',
+  displayName: 'Following',
+  uri: '',
+  route: {
+    href: '/',
+    name: 'Home',
+    params: {},
+  },
+  cid: '',
+  avatar: '',
+  description: new RichText({text: ''}),
+  creatorDid: '',
+  creatorHandle: '',
+  likeCount: 0,
+  likeUri: '',
+}
+
+export function usePinnedFeedsInfos(): FeedSourceInfo[] {
+  const {agent} = useSession()
+  const queryClient = useQueryClient()
+  const [tabs, setTabs] = React.useState<FeedSourceInfo[]>([
+    FOLLOWING_FEED_STUB,
+  ])
+  const {data: preferences} = usePreferencesQuery()
+  const pinnedFeedsKey = JSON.stringify(preferences?.feeds?.pinned)
+
+  React.useEffect(() => {
+    if (!preferences?.feeds?.pinned) return
+    const uris = preferences.feeds.pinned
+
+    async function fetchFeedInfo() {
+      const reqs = []
+
+      for (const uri of uris) {
+        const cached = queryClient.getQueryData<FeedSourceInfo>(
+          feedSourceInfoQueryKey({uri}),
+        )
+
+        if (cached) {
+          reqs.push(cached)
+        } else {
+          reqs.push(
+            queryClient.fetchQuery({
+              queryKey: feedSourceInfoQueryKey({uri}),
+              queryFn: async () => {
+                const type = getFeedTypeFromUri(uri)
+
+                if (type === 'feed') {
+                  const res = await agent.app.bsky.feed.getFeedGenerator({
+                    feed: uri,
+                  })
+                  return hydrateFeedGenerator(res.data.view)
+                } else {
+                  const res = await agent.app.bsky.graph.getList({
+                    list: uri,
+                    limit: 1,
+                  })
+                  return hydrateList(res.data.list)
+                }
+              },
+            }),
+          )
+        }
+      }
+
+      const views = await Promise.all(reqs)
+
+      setTabs([FOLLOWING_FEED_STUB].concat(views))
+    }
+
+    fetchFeedInfo()
+  }, [
+    agent,
+    queryClient,
+    setTabs,
+    preferences?.feeds?.pinned,
+    // ensure we react to re-ordering
+    pinnedFeedsKey,
+  ])
+
+  return tabs
+}
diff --git a/src/state/queries/preferences/types.ts b/src/state/queries/preferences/types.ts
index 9f4c30e53..2b04b725f 100644
--- a/src/state/queries/preferences/types.ts
+++ b/src/state/queries/preferences/types.ts
@@ -2,6 +2,7 @@ import {
   BskyPreferences,
   LabelPreference,
   BskyThreadViewPreference,
+  BskyFeedViewPreference,
 } from '@atproto/api'
 
 export type ConfigurableLabelGroup =
@@ -29,7 +30,9 @@ export type UsePreferencesQueryResponse = Omit<
    * we clean up the data in `usePreferencesQuery`.
    */
   contentLabels: Record<ConfigurableLabelGroup, LabelPreference>
-  feedViewPrefs: BskyPreferences['feedViewPrefs']['home']
+  feedViewPrefs: BskyFeedViewPreference & {
+    lab_mergeFeedEnabled: boolean
+  }
   /**
    * User thread-view prefs, including newer fields that may not be typed yet.
    */
diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx
index cd539406c..8dc3311fc 100644
--- a/src/view/com/modals/ContentFilteringSettings.tsx
+++ b/src/view/com/modals/ContentFilteringSettings.tsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import {BskyPreferences, LabelPreference} from '@atproto/api'
+import {LabelPreference} from '@atproto/api'
 import {StyleSheet, Pressable, View} from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
 import {observer} from 'mobx-react-lite'
@@ -23,6 +23,7 @@ import {
   usePreferencesSetAdultContentMutation,
   ConfigurableLabelGroup,
   CONFIGURABLE_LABEL_GROUPS,
+  UsePreferencesQueryResponse,
 } from '#/state/queries/preferences'
 
 export const snapPoints = ['90%']
@@ -175,7 +176,7 @@ const ContentLabelPref = observer(function ContentLabelPrefImpl({
   labelGroup,
   disabled,
 }: {
-  preferences?: BskyPreferences
+  preferences?: UsePreferencesQueryResponse
   labelGroup: ConfigurableLabelGroup
   disabled?: boolean
 }) {
diff --git a/src/view/com/pager/FeedsTabBar.web.tsx b/src/view/com/pager/FeedsTabBar.web.tsx
index 296af76e4..b48690a66 100644
--- a/src/view/com/pager/FeedsTabBar.web.tsx
+++ b/src/view/com/pager/FeedsTabBar.web.tsx
@@ -4,13 +4,12 @@ import Animated from 'react-native-reanimated'
 import {observer} from 'mobx-react-lite'
 import {TabBar} from 'view/com/pager/TabBar'
 import {RenderTabBarFnProps} from 'view/com/pager/Pager'
-import {useStores} from 'state/index'
-import {useHomeTabs} from 'lib/hooks/useHomeTabs'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile'
 import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
 import {useShellLayout} from '#/state/shell/shell-layout'
+import {usePinnedFeedsInfos} from '#/state/queries/feed'
 
 export const FeedsTabBar = observer(function FeedsTabBarImpl(
   props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
@@ -28,11 +27,11 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl(
 const FeedsTabBarTablet = observer(function FeedsTabBarTabletImpl(
   props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
 ) {
-  const store = useStores()
-  const items = useHomeTabs(store.preferences.pinnedFeeds)
+  const feeds = usePinnedFeedsInfos()
   const pal = usePalette('default')
   const {headerMinimalShellTransform} = useMinimalShellMode()
   const {headerHeight} = useShellLayout()
+  const items = feeds.map(f => f.displayName)
 
   return (
     // @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx
index d79bfe94e..de985fb7c 100644
--- a/src/view/com/pager/FeedsTabBarMobile.tsx
+++ b/src/view/com/pager/FeedsTabBarMobile.tsx
@@ -3,8 +3,6 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native'
 import {observer} from 'mobx-react-lite'
 import {TabBar} from 'view/com/pager/TabBar'
 import {RenderTabBarFnProps} from 'view/com/pager/Pager'
-import {useStores} from 'state/index'
-import {useHomeTabs} from 'lib/hooks/useHomeTabs'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
 import {Link} from '../util/Link'
@@ -20,19 +18,20 @@ import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
 import {useSetDrawerOpen} from '#/state/shell/drawer-open'
 import {useShellLayout} from '#/state/shell/shell-layout'
 import {useSession} from '#/state/session'
+import {usePinnedFeedsInfos} from '#/state/queries/feed'
 
 export const FeedsTabBar = observer(function FeedsTabBarImpl(
   props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
 ) {
   const pal = usePalette('default')
-  const store = useStores()
   const {isSandbox} = useSession()
   const {_} = useLingui()
   const setDrawerOpen = useSetDrawerOpen()
-  const items = useHomeTabs(store.preferences.pinnedFeeds)
+  const feeds = usePinnedFeedsInfos()
   const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3)
   const {headerHeight} = useShellLayout()
   const {headerMinimalShellTransform} = useMinimalShellMode()
+  const items = feeds.map(f => f.displayName)
 
   const onPressAvi = React.useCallback(() => {
     setDrawerOpen(true)
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index c78f44cd1..5f60322bd 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -508,7 +508,7 @@ function SavedFeed({feedUri}: {feedUri: string}) {
   return (
     <Link
       testID={`saved-feed-${info.displayName}`}
-      href={info.href}
+      href={info.route.href}
       style={[pal.border, styles.savedFeed, isMobile && styles.savedFeedMobile]}
       hoverStyle={pal.viewLight}
       accessibilityLabel={info.displayName}
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 53813f822..fdd764e44 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -1,7 +1,6 @@
 import React from 'react'
 import {useFocusEffect} from '@react-navigation/native'
 import {observer} from 'mobx-react-lite'
-import isEqual from 'lodash.isequal'
 import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
 import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
@@ -13,6 +12,7 @@ import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
 import {useStores} from 'state/index'
 import {FeedPage} from 'view/com/feeds/FeedPage'
 import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell'
+import {usePreferencesQuery} from '#/state/queries/preferences'
 
 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
 export const HomeScreen = withAuthRequired(
@@ -23,19 +23,15 @@ export const HomeScreen = withAuthRequired(
     const pagerRef = React.useRef<PagerRef>(null)
     const [selectedPage, setSelectedPage] = React.useState(0)
     const [customFeeds, setCustomFeeds] = React.useState<FeedDescriptor[]>([])
-    const [requestedCustomFeeds, setRequestedCustomFeeds] = React.useState<
-      string[]
-    >([])
+    const {data: preferences} = usePreferencesQuery()
 
     React.useEffect(() => {
-      const pinned = store.preferences.pinnedFeeds
+      if (!preferences?.feeds?.pinned) return
 
-      if (isEqual(pinned, requestedCustomFeeds)) {
-        // no changes
-        return
-      }
+      const pinned = preferences.feeds.pinned
 
       const feeds: FeedDescriptor[] = []
+
       for (const uri of pinned) {
         if (uri.includes('app.bsky.feed.generator')) {
           feeds.push(`feedgen|${uri}`)
@@ -43,31 +39,20 @@ export const HomeScreen = withAuthRequired(
           feeds.push(`list|${uri}`)
         }
       }
-      pagerRef.current?.setPage(0)
+
       setCustomFeeds(feeds)
-      setRequestedCustomFeeds(pinned)
-    }, [
-      store,
-      store.preferences.pinnedFeeds,
-      customFeeds,
-      setCustomFeeds,
-      pagerRef,
-      requestedCustomFeeds,
-      setRequestedCustomFeeds,
-    ])
+
+      pagerRef.current?.setPage(0)
+    }, [preferences?.feeds?.pinned, setCustomFeeds, pagerRef])
 
     const homeFeedParams = React.useMemo<FeedParams>(() => {
-      if (!store.preferences.homeFeed.lab_mergeFeedEnabled) {
-        return {}
-      }
+      if (!preferences) return {}
+
       return {
-        mergeFeedEnabled: true,
-        mergeFeedSources: store.preferences.savedFeeds,
+        mergeFeedEnabled: preferences.feedViewPrefs.lab_mergeFeedEnabled,
+        mergeFeedSources: preferences.feeds.saved,
       }
-    }, [
-      store.preferences.homeFeed.lab_mergeFeedEnabled,
-      store.preferences.savedFeeds,
-    ])
+    }, [preferences])
 
     useFocusEffect(
       React.useCallback(() => {
diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx
index 3237d2cdd..9cb10517e 100644
--- a/src/view/shell/desktop/Feeds.tsx
+++ b/src/view/shell/desktop/Feeds.tsx
@@ -2,16 +2,14 @@ import React from 'react'
 import {View, StyleSheet} from 'react-native'
 import {useNavigationState} from '@react-navigation/native'
 import {observer} from 'mobx-react-lite'
-import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
-import {useDesktopRightNavItems} from 'lib/hooks/useDesktopRightNavItems'
 import {TextLink} from 'view/com/util/Link'
 import {getCurrentRoute} from 'lib/routes/helpers'
+import {usePinnedFeedsInfos} from '#/state/queries/feed'
 
 export const DesktopFeeds = observer(function DesktopFeeds() {
-  const store = useStores()
   const pal = usePalette('default')
-  const items = useDesktopRightNavItems(store.preferences.pinnedFeeds)
+  const feeds = usePinnedFeedsInfos()
 
   const route = useNavigationState(state => {
     if (!state) {
@@ -23,29 +21,29 @@ export const DesktopFeeds = observer(function DesktopFeeds() {
   return (
     <View style={[styles.container, pal.view, pal.border]}>
       <FeedItem href="/" title="Following" current={route.name === 'Home'} />
-      {items.map(item => {
-        try {
-          const params = route.params as Record<string, string>
-          const routeName =
-            item.collection === 'app.bsky.feed.generator'
-              ? 'ProfileFeed'
-              : 'ProfileList'
-          return (
-            <FeedItem
-              key={item.uri}
-              href={item.href}
-              title={item.displayName}
-              current={
-                route.name === routeName &&
-                params.name === item.hostname &&
-                params.rkey === item.rkey
-              }
-            />
-          )
-        } catch {
-          return null
-        }
-      })}
+      {feeds
+        .filter(f => f.displayName !== 'Following')
+        .map(feed => {
+          try {
+            const params = route.params as Record<string, string>
+            const routeName =
+              feed.type === 'feed' ? 'ProfileFeed' : 'ProfileList'
+            return (
+              <FeedItem
+                key={feed.uri}
+                href={feed.route.href}
+                title={feed.displayName}
+                current={
+                  route.name === routeName &&
+                  params.name === feed.route.params.name &&
+                  params.rkey === feed.route.params.rkey
+                }
+              />
+            )
+          } catch {
+            return null
+          }
+        })}
       <View style={{paddingTop: 8, paddingBottom: 6}}>
         <TextLink
           type="lg"