about summary refs log tree commit diff
path: root/src/state/queries/feed.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/queries/feed.ts')
-rw-r--r--src/state/queries/feed.ts139
1 files changed, 139 insertions, 0 deletions
diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts
index 83d6a7634..972dbf995 100644
--- a/src/state/queries/feed.ts
+++ b/src/state/queries/feed.ts
@@ -9,20 +9,24 @@ import {
 } from '@atproto/api'
 import {
   InfiniteData,
+  QueryClient,
   QueryKey,
   useInfiniteQuery,
   useMutation,
   useQuery,
+  useQueryClient,
 } from '@tanstack/react-query'
 
 import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
 import {sanitizeDisplayName} from '#/lib/strings/display-names'
 import {sanitizeHandle} from '#/lib/strings/handles'
 import {STALE} from '#/state/queries'
+import {RQKEY as listQueryKey} from '#/state/queries/list'
 import {usePreferencesQuery} from '#/state/queries/preferences'
 import {useAgent, useSession} from '#/state/session'
 import {router} from '#/routes'
 import {FeedDescriptor} from './post-feed'
+import {precacheResolvedUri} from './resolve-uri'
 
 export type FeedSourceFeedInfo = {
   type: 'feed'
@@ -201,6 +205,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
   const agent = useAgent()
   const limit = options?.limit || 10
   const {data: preferences} = usePreferencesQuery()
+  const queryClient = useQueryClient()
 
   // Make sure this doesn't invalidate unless really needed.
   const selectArgs = useMemo(
@@ -225,6 +230,13 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
         limit,
         cursor: pageParam,
       })
+
+      // precache feeds
+      for (const feed of res.data.feeds) {
+        const hydratedFeed = hydrateFeedGenerator(feed)
+        precacheFeed(queryClient, hydratedFeed)
+      }
+
       return res.data
     },
     initialPageParam: undefined,
@@ -449,3 +461,130 @@ export function usePinnedFeedsInfos() {
     },
   })
 }
+
+export type SavedFeedItem =
+  | {
+      type: 'feed'
+      config: AppBskyActorDefs.SavedFeed
+      view: AppBskyFeedDefs.GeneratorView
+    }
+  | {
+      type: 'list'
+      config: AppBskyActorDefs.SavedFeed
+      view: AppBskyGraphDefs.ListView
+    }
+  | {
+      type: 'timeline'
+      config: AppBskyActorDefs.SavedFeed
+      view: undefined
+    }
+
+export function useSavedFeeds() {
+  const agent = useAgent()
+  const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery()
+  const savedItems = preferences?.savedFeeds ?? []
+  const queryClient = useQueryClient()
+
+  return useQuery({
+    staleTime: STALE.INFINITY,
+    enabled: !isLoadingPrefs,
+    queryKey: [pinnedFeedInfosQueryKeyRoot, ...savedItems],
+    placeholderData: previousData => {
+      return (
+        previousData || {
+          count: savedItems.length,
+          feeds: [],
+        }
+      )
+    },
+    queryFn: async () => {
+      const resolvedFeeds = new Map<string, AppBskyFeedDefs.GeneratorView>()
+      const resolvedLists = new Map<string, AppBskyGraphDefs.ListView>()
+
+      const savedFeeds = savedItems.filter(feed => feed.type === 'feed')
+      const savedLists = savedItems.filter(feed => feed.type === 'list')
+
+      let feedsPromise = Promise.resolve()
+      if (savedFeeds.length > 0) {
+        feedsPromise = agent.app.bsky.feed
+          .getFeedGenerators({
+            feeds: savedFeeds.map(f => f.value),
+          })
+          .then(res => {
+            res.data.feeds.forEach(f => {
+              resolvedFeeds.set(f.uri, f)
+            })
+          })
+      }
+
+      const listsPromises = savedLists.map(list =>
+        agent.app.bsky.graph
+          .getList({
+            list: list.value,
+            limit: 1,
+          })
+          .then(res => {
+            const listView = res.data.list
+            resolvedLists.set(listView.uri, listView)
+          }),
+      )
+
+      await Promise.allSettled([feedsPromise, ...listsPromises])
+
+      resolvedFeeds.forEach(feed => {
+        const hydratedFeed = hydrateFeedGenerator(feed)
+        precacheFeed(queryClient, hydratedFeed)
+      })
+      resolvedLists.forEach(list => {
+        precacheList(queryClient, list)
+      })
+
+      const res: SavedFeedItem[] = savedItems.map(s => {
+        if (s.type === 'timeline') {
+          return {
+            type: 'timeline',
+            config: s,
+            view: undefined,
+          }
+        }
+
+        return {
+          type: s.type,
+          config: s,
+          view:
+            s.type === 'feed'
+              ? resolvedFeeds.get(s.value)
+              : resolvedLists.get(s.value),
+        }
+      }) as SavedFeedItem[]
+
+      return {
+        count: savedItems.length,
+        feeds: res,
+      }
+    },
+  })
+}
+
+function precacheFeed(queryClient: QueryClient, hydratedFeed: FeedSourceInfo) {
+  precacheResolvedUri(
+    queryClient,
+    hydratedFeed.creatorHandle,
+    hydratedFeed.creatorDid,
+  )
+  queryClient.setQueryData<FeedSourceInfo>(
+    feedSourceInfoQueryKey({uri: hydratedFeed.uri}),
+    hydratedFeed,
+  )
+}
+
+function precacheList(
+  queryClient: QueryClient,
+  list: AppBskyGraphDefs.ListView,
+) {
+  precacheResolvedUri(queryClient, list.creator.handle, list.creator.did)
+  queryClient.setQueryData<AppBskyGraphDefs.ListView>(
+    listQueryKey(list.uri),
+    list,
+  )
+}