diff options
author | Eric Bailey <git@esb.lol> | 2025-04-04 18:44:02 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-04 16:44:02 -0700 |
commit | aca89d4aea61a50697187464e88ac1b9a4ef40bd (patch) | |
tree | e7f7459996b9ea3815bc909e7f0a154ad1961a58 | |
parent | a0ff9b52aad3349b24118a0222e0f3d78e695887 (diff) | |
download | voidsky-aca89d4aea61a50697187464e88ac1b9a4ef40bd.tar.zst |
[Explore] New suggested follows endpoint (#8130)
* Bump SDK * Integrate new endpoint, add profile shadow, For You tab * Format
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/screens/Search/Explore.tsx | 129 | ||||
-rw-r--r-- | src/screens/Search/modules/ExploreSuggestedAccounts.tsx | 2 | ||||
-rw-r--r-- | src/state/cache/profile-shadow.ts | 2 | ||||
-rw-r--r-- | src/state/queries/trending/useGetSuggestedUsersQuery.ts | 71 | ||||
-rw-r--r-- | yarn.lock | 8 |
6 files changed, 108 insertions, 106 deletions
diff --git a/package.json b/package.json index 6f73a5581..2ff7557d1 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.14.19", + "@atproto/api": "^0.14.20", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index 088bc5724..8c41a507a 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -15,7 +15,6 @@ import {sanitizeHandle} from '#/lib/strings/handles' import {logger} from '#/logger' import {type MetricEvents} from '#/logger/metrics' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useActorSearchPaginated} from '#/state/queries/actor-search' import { type FeedPreviewItem, useFeedPreviews, @@ -23,8 +22,8 @@ import { import {useGetPopularFeedsQuery} from '#/state/queries/feed' import {Nux, useNux} from '#/state/queries/nuxs' import {usePreferencesQuery} from '#/state/queries/preferences' -import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows' import {useGetSuggestedFeedsQuery} from '#/state/queries/trending/useGetSuggestedFeedsQuery' +import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery' import {useSuggestedStarterPacksQuery} from '#/state/queries/useSuggestedStarterPacksQuery' import {useProgressGuide} from '#/state/shell/progress-guide' import {isThreadChildAt, isThreadParentAt} from '#/view/com/posts/PostFeed' @@ -57,7 +56,6 @@ import * as ModuleHeader from './components/ModuleHeader' import { SuggestedAccountsTabBar, SuggestedProfileCard, - useLoadEnoughProfiles, } from './modules/ExploreSuggestedAccounts' function LoadMore({item}: {item: ExploreScreenItems & {type: 'loadMore'}}) { @@ -144,7 +142,7 @@ type ExploreScreenItems = | { type: 'profile' key: string - profile: AppBskyActorDefs.ProfileView + profile: AppBskyActorDefs.ProfileViewBasic recId?: number } | { @@ -203,33 +201,13 @@ export function Explore({ const gate = useGate() const guide = useProgressGuide('follow-10') const [selectedInterest, setSelectedInterest] = useState<string | null>(null) + // TODO always get at least 10 back const { - data: suggestedProfiles, - hasNextPage: hasNextSuggestedProfilesPage, - isLoading: isLoadingSuggestedProfiles, - isFetchingNextPage: isFetchingNextSuggestedProfilesPage, - error: suggestedProfilesError, - fetchNextPage: fetchNextSuggestedProfilesPage, - } = useSuggestedFollowsQuery({limit: 3, subsequentPageLimit: 10}) - const { - data: interestProfiles, - hasNextPage: hasNextInterestProfilesPage, - isLoading: isLoadingInterestProfiles, - isFetchingNextPage: isFetchingNextInterestProfilesPage, - error: interestProfilesError, - fetchNextPage: fetchNextInterestProfilesPage, - } = useActorSearchPaginated({ - query: selectedInterest || '', - enabled: !!selectedInterest, - limit: 10, - }) - const {isReady: canShowSuggestedProfiles} = useLoadEnoughProfiles({ - interest: selectedInterest, - data: interestProfiles, - isLoading: isLoadingInterestProfiles, - isFetchingNextPage: isFetchingNextInterestProfilesPage, - hasNextPage: hasNextInterestProfilesPage, - fetchNextPage: fetchNextInterestProfilesPage, + data: suggestedUsers, + isLoading: suggestedUsersIsLoading, + error: suggestedUsersError, + } = useGetSuggestedUsersQuery({ + category: selectedInterest, }) const { data: feeds, @@ -243,39 +221,6 @@ export function Explore({ const showInterestsNux = interestsNux.status === 'ready' && !interestsNux.nux?.completed - const profiles: typeof suggestedProfiles & typeof interestProfiles = - !selectedInterest ? suggestedProfiles : interestProfiles - const hasNextProfilesPage = !selectedInterest - ? hasNextSuggestedProfilesPage - : hasNextInterestProfilesPage - const isLoadingProfiles = !selectedInterest - ? isLoadingSuggestedProfiles - : !canShowSuggestedProfiles - const isFetchingNextProfilesPage = !selectedInterest - ? isFetchingNextSuggestedProfilesPage - : !canShowSuggestedProfiles - const profilesError = !selectedInterest - ? suggestedProfilesError - : interestProfilesError - const fetchNextProfilesPage = !selectedInterest - ? fetchNextSuggestedProfilesPage - : fetchNextInterestProfilesPage - - const isLoadingMoreProfiles = isFetchingNextProfilesPage && !isLoadingProfiles - const onLoadMoreProfiles = useCallback(async () => { - if (isFetchingNextProfilesPage || !hasNextProfilesPage || profilesError) - return - try { - await fetchNextProfilesPage() - } catch (err) { - logger.error('Failed to load more suggested follows', {message: err}) - } - }, [ - isFetchingNextProfilesPage, - hasNextProfilesPage, - profilesError, - fetchNextProfilesPage, - ]) const { data: suggestedSPs, isLoading: isLoadingSuggestedSPs, @@ -358,55 +303,42 @@ export function Explore({ }, }) - if (!canShowSuggestedProfiles) { + if (suggestedUsersIsLoading) { i.push({type: 'profilePlaceholder', key: 'profilePlaceholder'}) - } else if (profilesError) { + } else if (suggestedUsersError) { i.push({ type: 'error', - key: 'profilesError', + key: 'suggestedUsersError', message: _(msg`Failed to load suggested follows`), - error: cleanError(profilesError), + error: cleanError(suggestedUsersError), }) } else { - if (profiles !== undefined) { - if (profiles.pages.length > 0 && moderationOpts) { + if (suggestedUsers !== undefined) { + if (suggestedUsers.actors.length > 0 && moderationOpts) { // Currently the responses contain duplicate items. // Needs to be fixed on backend, but let's dedupe to be safe. let seen = new Set() const profileItems: ExploreScreenItems[] = [] - for (const page of profiles.pages) { - for (const actor of page.actors) { - if (!seen.has(actor.did) && !actor.viewer?.following) { - seen.add(actor.did) - profileItems.push({ - type: 'profile', - key: actor.did, - profile: actor, - recId: page.recId, - }) - } + for (const actor of suggestedUsers.actors) { + if (!seen.has(actor.did) && !actor.viewer?.following) { + seen.add(actor.did) + profileItems.push({ + type: 'profile', + key: actor.did, + profile: actor, + }) } } if (profileItems.length === 0) { - if (!hasNextProfilesPage) { - // no items! remove the header - i.pop() - } + // no items! remove the header + i.pop() } else { i.push(...profileItems) } - if (hasNextProfilesPage) { - i.push({ - type: 'loadMore', - key: 'loadMoreProfiles', - message: _(msg`Load more suggested accounts`), - isLoadingMore: isLoadingMoreProfiles, - onLoadMore: onLoadMoreProfiles, - }) - } } else { - console.log('no pages') + // no items! remove the header + i.pop() } } else { i.push({type: 'profilePlaceholder', key: 'profilePlaceholder'}) @@ -414,14 +346,11 @@ export function Explore({ } return i }, [ - profiles, _, - canShowSuggestedProfiles, - hasNextProfilesPage, - isLoadingMoreProfiles, moderationOpts, - onLoadMoreProfiles, - profilesError, + suggestedUsers, + suggestedUsersIsLoading, + suggestedUsersError, ]) const suggestedFeedsModule = useMemo(() => { const i: ExploreScreenItems[] = [] diff --git a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx index 070d75910..71210823e 100644 --- a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx +++ b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx @@ -83,7 +83,7 @@ export function SuggestedAccountsTabBar({ }} hasSearchText={false} interestsDisplayNames={{ - all: _(msg`All`), + all: _(msg`For You`), ...interestsDisplayNames, }} TabComponent={Tab} diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts index 84ebc565c..82ee44388 100644 --- a/src/state/cache/profile-shadow.ts +++ b/src/state/cache/profile-shadow.ts @@ -19,6 +19,7 @@ import {findAllProfilesInQueryData as findAllProfilesInProfileQueryData} from '# import {findAllProfilesInQueryData as findAllProfilesInProfileFollowersQueryData} from '#/state/queries/profile-followers' import {findAllProfilesInQueryData as findAllProfilesInProfileFollowsQueryData} from '#/state/queries/profile-follows' import {findAllProfilesInQueryData as findAllProfilesInSuggestedFollowsQueryData} from '#/state/queries/suggested-follows' +import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersQueryData} from '#/state/queries/trending/useGetSuggestedUsersQuery' import type * as bsky from '#/types/bsky' import {castAsShadow, type Shadow} from './types' @@ -149,6 +150,7 @@ function* findProfilesInCache( yield* findAllProfilesInProfileQueryData(queryClient, did) yield* findAllProfilesInProfileFollowersQueryData(queryClient, did) yield* findAllProfilesInProfileFollowsQueryData(queryClient, did) + yield* findAllProfilesInSuggestedUsersQueryData(queryClient, did) yield* findAllProfilesInSuggestedFollowsQueryData(queryClient, did) yield* findAllProfilesInActorSearchQueryData(queryClient, did) yield* findAllProfilesInListConvosQueryData(queryClient, did) diff --git a/src/state/queries/trending/useGetSuggestedUsersQuery.ts b/src/state/queries/trending/useGetSuggestedUsersQuery.ts new file mode 100644 index 000000000..eb97ad666 --- /dev/null +++ b/src/state/queries/trending/useGetSuggestedUsersQuery.ts @@ -0,0 +1,71 @@ +import { + type AppBskyActorDefs, + type AppBskyUnspeccedGetSuggestedUsers, +} from '@atproto/api' +import {type QueryClient, useQuery} from '@tanstack/react-query' + +import { + aggregateUserInterests, + createBskyTopicsHeader, +} from '#/lib/api/feed/utils' +import {getContentLanguages} from '#/state/preferences/languages' +import {STALE} from '#/state/queries' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {useAgent} from '#/state/session' + +export type QueryProps = {category?: string | null} + +export const getSuggestedUsersQueryKeyRoot = 'unspecced-suggested-users' +export const createGetSuggestedUsersQueryKey = (props: QueryProps) => [ + getSuggestedUsersQueryKeyRoot, + ...Object.values(props), +] + +export function useGetSuggestedUsersQuery(props: QueryProps) { + const agent = useAgent() + const {data: preferences} = usePreferencesQuery() + + return useQuery({ + enabled: !!preferences, + refetchOnWindowFocus: true, + staleTime: STALE.MINUTES.ONE, + queryKey: createGetSuggestedUsersQueryKey(props), + queryFn: async () => { + const contentLangs = getContentLanguages().join(',') + const {data} = await agent.app.bsky.unspecced.getSuggestedUsers( + { + category: props.category ?? undefined, + }, + { + headers: { + ...createBskyTopicsHeader(aggregateUserInterests(preferences)), + 'Accept-Language': contentLangs, + }, + }, + ) + + return data + }, + }) +} + +export function* findAllProfilesInQueryData( + queryClient: QueryClient, + did: string, +): Generator<AppBskyActorDefs.ProfileViewBasic, void> { + const responses = + queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsers.OutputSchema>({ + queryKey: [getSuggestedUsersQueryKeyRoot], + }) + for (const [_, response] of responses) { + if (!response) { + continue + } + + for (const actor of response.actors) { + if (actor.did === did) { + yield actor + } + } + } +} diff --git a/yarn.lock b/yarn.lock index 0541080f6..88b7c8388 100644 --- a/yarn.lock +++ b/yarn.lock @@ -80,10 +80,10 @@ tlds "^1.234.0" zod "^3.23.8" -"@atproto/api@^0.14.19": - version "0.14.19" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.14.19.tgz#fef8994e2b14e69a9e3a0aef043c7fcb34d6bf8c" - integrity sha512-YYTqM0K0qk2TP7PguktPzlAQGLTL1bEGz6PgY5kqKJNX4o1318kJYB22DzjJYqV2NUCq0JQ9Lb0oskLvTisEOg== +"@atproto/api@^0.14.20": + version "0.14.20" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.14.20.tgz#904c85a91748f3203fd929415cb8fb3bc78d35d3" + integrity sha512-Daip22+u9N+EVPk9PsEEVrTfjIqGczXnAT7o2EHGd0JsOzMbp3a6wmW1beKqYDzPf+Dc36/39JeUYYqhB3fKjg== dependencies: "@atproto/common-web" "^0.4.1" "@atproto/lexicon" "^0.4.10" |