diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index b599ac1a..fed23f5b 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -1,3 +1,4 @@ +import {useCallback, useEffect, useMemo, useRef} from 'react' import { AppBskyActorDefs, AppBskyFeedDefs, @@ -171,28 +172,117 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) { }) } -export const useGetPopularFeedsQueryKey = ['getPopularFeeds'] +// HACK +// the protocol doesn't yet tell us which feeds are personalized +// this list is used to filter out feed recommendations from logged out users +// for the ones we know need it +// -prf +export const KNOWN_AUTHED_ONLY_FEEDS = [ + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app + 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed + 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed + 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow + 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky + 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz + 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why +] -export function useGetPopularFeedsQuery() { +type GetPopularFeedsOptions = {limit?: number} + +export function createGetPopularFeedsQueryKey(...args: any[]) { + return ['getPopularFeeds', ...args] +} + +export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { + const {hasSession} = useSession() const agent = useAgent() - return useInfiniteQuery< + const limit = options?.limit || 10 + const {data: preferences} = usePreferencesQuery() + + // Make sure this doesn't invalidate unless really needed. + const selectArgs = useMemo( + () => ({ + hasSession, + savedFeeds: preferences?.savedFeeds || [], + }), + [hasSession, preferences?.savedFeeds], + ) + const lastPageCountRef = useRef(0) + + const query = useInfiniteQuery< AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema, Error, InfiniteData, QueryKey, string | undefined >({ - queryKey: useGetPopularFeedsQueryKey, + queryKey: createGetPopularFeedsQueryKey(options), queryFn: async ({pageParam}) => { const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({ - limit: 10, + limit, cursor: pageParam, }) return res.data }, initialPageParam: undefined, getNextPageParam: lastPage => lastPage.cursor, + select: useCallback( + ( + data: InfiniteData, + ) => { + const {savedFeeds, hasSession: hasSessionInner} = selectArgs + data?.pages.map(page => { + page.feeds = page.feeds.filter(feed => { + if ( + !hasSessionInner && + KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri) + ) { + return false + } + const alreadySaved = Boolean( + savedFeeds?.find(f => { + return f.value === feed.uri + }), + ) + return !alreadySaved + }) + + return page + }) + + return data + }, + [selectArgs /* Don't change. Everything needs to go into selectArgs. */], + ), }) + + useEffect(() => { + const {isFetching, hasNextPage, data} = query + if (isFetching || !hasNextPage) { + return + } + + // avoid double-fires of fetchNextPage() + if ( + lastPageCountRef.current !== 0 && + lastPageCountRef.current === data?.pages?.length + ) { + return + } + + // fetch next page if we haven't gotten a full page of content + let count = 0 + for (const page of data?.pages || []) { + count += page.feeds.length + } + if (count < limit && (data?.pages.length || 0) < 6) { + query.fetchNextPage() + lastPageCountRef.current = data?.pages?.length || 0 + } + }, [query, limit]) + + return query } export function useSearchPopularFeedsMutation() { diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 76ff4268..61255945 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -104,22 +104,6 @@ type FlatlistSlice = key: string } -// HACK -// the protocol doesn't yet tell us which feeds are personalized -// this list is used to filter out feed recommendations from logged out users -// for the ones we know need it -// -prf -const KNOWN_AUTHED_ONLY_FEEDS = [ - 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app - 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed - 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed - 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow - 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz - 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky - 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz - 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why -] - export function FeedsScreen(_props: Props) { const pal = usePalette('default') const {openComposer} = useComposerControls() @@ -327,10 +311,7 @@ export function FeedsScreen(_props: Props) { type: 'popularFeedsLoading', }) } else { - if ( - !popularFeeds?.pages || - popularFeeds?.pages[0]?.feeds?.length === 0 - ) { + if (!popularFeeds?.pages) { slices.push({ key: 'popularFeedsNoResults', type: 'popularFeedsNoResults', @@ -338,26 +319,11 @@ export function FeedsScreen(_props: Props) { } else { for (const page of popularFeeds.pages || []) { slices = slices.concat( - page.feeds - .filter(feed => { - if ( - !hasSession && - KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri) - ) { - return false - } - const alreadySaved = Boolean( - preferences?.savedFeeds?.find(f => { - return f.value === feed.uri - }), - ) - return !alreadySaved - }) - .map(feed => ({ - key: `popularFeed:${feed.uri}`, - type: 'popularFeed', - feedUri: feed.uri, - })), + page.feeds.map(feed => ({ + key: `popularFeed:${feed.uri}`, + type: 'popularFeed', + feedUri: feed.uri, + })), ) }