include 'for you' in see more suggested users

This commit is contained in:
vineyardbovines
2026-04-13 09:20:56 -04:00
parent 5d0b900719
commit e14dd07c7c
3 changed files with 105 additions and 24 deletions
+59 -16
View File
@@ -2,12 +2,17 @@ import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {TextInput, View, type ViewToken} from 'react-native' import {TextInput, View, type ViewToken} from 'react-native'
import {type ModerationOpts} from '@atproto/api' import {type ModerationOpts} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorSearch} from '#/state/queries/actor-search' import {useActorSearch} from '#/state/queries/actor-search'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {
getAllDidsInDiscoverCache,
useGetSuggestedUsersForDiscoverQuery,
} from '#/state/queries/trending/useGetSuggestedUsersForDiscoverQuery'
import {useGetSuggestedUsersForSeeMoreQuery} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' import {useGetSuggestedUsersForSeeMoreQuery} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {type Follow10ProgressGuide} from '#/state/shell/progress-guide' import {type Follow10ProgressGuide} from '#/state/shell/progress-guide'
@@ -109,21 +114,33 @@ export function FollowDialogWithoutGuide({
let lastSelectedInterest = '' let lastSelectedInterest = ''
let lastSearchText = '' let lastSearchText = ''
const FOR_YOU_TAB = 'all'
function DialogInner({guide}: {guide?: Follow10ProgressGuide}) { function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
const {t: l} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const interestsDisplayNames = useInterestsDisplayNames() const queryClient = useQueryClient()
const rawInterestsDisplayNames = useInterestsDisplayNames()
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const personalizedInterests = preferences?.interests?.tags const personalizedInterests = preferences?.interests?.tags
const interests = Object.keys(interestsDisplayNames) const interests = useMemo(
.sort(boostInterests(popularInterests)) () => [
.sort(boostInterests(personalizedInterests)) FOR_YOU_TAB,
...Object.keys(rawInterestsDisplayNames)
.sort(boostInterests(popularInterests))
.sort(boostInterests(personalizedInterests)),
],
[rawInterestsDisplayNames, personalizedInterests],
)
const interestsDisplayNames = useMemo(
() => ({
[FOR_YOU_TAB]: l`For You`,
...rawInterestsDisplayNames,
}),
[l, rawInterestsDisplayNames],
)
const [selectedInterest, setSelectedInterest] = useState( const [selectedInterest, setSelectedInterest] = useState(
() => () => lastSelectedInterest || FOR_YOU_TAB,
lastSelectedInterest ||
(personalizedInterests && interests.includes(personalizedInterests[0])
? personalizedInterests[0]
: interests[0]),
) )
const [searchText, setSearchText] = useState(lastSearchText) const [searchText, setSearchText] = useState(lastSearchText)
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
@@ -137,14 +154,31 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
lastSelectedInterest = selectedInterest lastSelectedInterest = selectedInterest
}, [searchText, selectedInterest]) }, [searchText, selectedInterest])
const { const isForYou = selectedInterest === FOR_YOU_TAB
data: suggestions,
isFetching: isFetchingSuggestions, // Snapshot the DIDs already shown by the home-feed Discover interstitial at
error: suggestionsError, // dialog open time. The endpoint has no cursor; we dedup client-side so the
} = useGetSuggestedUsersForSeeMoreQuery({ // For You tab doesn't re-show profiles the viewer just saw. Snapshotting
category: selectedInterest, // once (lazy init) prevents our own `limit: 50` fetch from excluding itself
// after it lands in the shared cache.
const [alreadyShownDids] = useState(() =>
getAllDidsInDiscoverCache(queryClient),
)
const discoverQuery = useGetSuggestedUsersForDiscoverQuery({
limit: 50, limit: 50,
enabled: isForYou,
}) })
const seeMoreQuery = useGetSuggestedUsersForSeeMoreQuery({
category: isForYou ? undefined : selectedInterest,
limit: 50,
enabled: !isForYou,
})
const suggestions = isForYou ? discoverQuery.data : seeMoreQuery.data
const isFetchingSuggestions = isForYou
? discoverQuery.isFetching
: seeMoreQuery.isFetching
const suggestionsError = isForYou ? discoverQuery.error : seeMoreQuery.error
const { const {
data: searchResults, data: searchResults,
isFetching: isFetchingSearchResults, isFetching: isFetchingSearchResults,
@@ -188,6 +222,10 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
if (seen.has(profile.did)) continue if (seen.has(profile.did)) continue
if (profile.did === currentAccount?.did) continue if (profile.did === currentAccount?.did) continue
if (profile.viewer?.following) continue if (profile.viewer?.following) continue
// On the For You tab, skip profiles the viewer was already shown by
// the home-feed Discover interstitial.
if (isForYou && !hasSearchText && alreadyShownDids.has(profile.did))
continue
seen.add(profile.did) seen.add(profile.did)
@@ -222,6 +260,8 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
hasSearchText, hasSearchText,
resultsKey, resultsKey,
isSearchResultsError, isSearchResultsError,
isForYou,
alreadyShownDids,
]) ])
const isGuide = Boolean(guide) const isGuide = Boolean(guide)
@@ -277,7 +317,10 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
recId: recIdForLogging, recId: recIdForLogging,
position: position !== -1 ? position : 0, position: position !== -1 ? position : 0,
suggestedDid: item.profile.did, suggestedDid: item.profile.did,
category: selectedInterestRef.current, category:
selectedInterestRef.current === FOR_YOU_TAB
? null
: selectedInterestRef.current,
}) })
} }
} }
@@ -15,21 +15,23 @@ import {useAgent} from '#/state/session'
export type QueryProps = { export type QueryProps = {
limit?: number limit?: number
enabled?: boolean
} }
export const getSuggestedUsersForDiscoverQueryKeyRoot = export const getSuggestedUsersForDiscoverQueryKeyRoot =
'unspecced-suggested-users-for-explore' 'unspecced-suggested-users-for-explore'
export const createGetSuggestedUsersForDiscoverQueryKey = ( export const createGetSuggestedUsersForDiscoverQueryKey = (props: {
props: QueryProps, limit?: number
) => [getSuggestedUsersForDiscoverQueryKeyRoot, props.limit] }) => [getSuggestedUsersForDiscoverQueryKeyRoot, props.limit]
export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) { export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
const agent = useAgent() const agent = useAgent()
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
return useQuery({ return useQuery({
enabled: props.enabled ?? true,
staleTime: STALE.MINUTES.THREE, staleTime: STALE.MINUTES.THREE,
queryKey: createGetSuggestedUsersForDiscoverQueryKey(props), queryKey: createGetSuggestedUsersForDiscoverQueryKey({limit: props.limit}),
queryFn: async () => { queryFn: async () => {
const contentLangs = getContentLanguages().join(',') const contentLangs = getContentLanguages().join(',')
const userInterests = aggregateUserInterests(preferences) const userInterests = aggregateUserInterests(preferences)
@@ -73,3 +75,33 @@ export function* findAllProfilesInQueryData(
} }
} }
} }
/**
* Collects every DID currently cached under any `getSuggestedUsersForDiscover`
* query. Used by the See More dialog's For You tab to filter out profiles the
* viewer has already been shown by the home-feed interstitial (the endpoint
* has no cursor/offset, so we dedup client-side).
*
* The query key root is shared with `getSuggestedUsersForSeeMore`, so we
* narrow by key length (Discover keys are `[root, limit]`, SeeMore keys are
* `[root, category, limit]`).
*/
export function getAllDidsInDiscoverCache(
queryClient: QueryClient,
): Set<string> {
const dids = new Set<string>()
const responses =
queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsersForDiscover.OutputSchema>(
{
queryKey: [getSuggestedUsersForDiscoverQueryKeyRoot],
},
)
for (const [key, response] of responses) {
if (!response) continue
if (key.length !== 2) continue
for (const actor of response.actors) {
dids.add(actor.did)
}
}
return dids
}
@@ -16,21 +16,27 @@ import {useAgent} from '#/state/session'
export type QueryProps = { export type QueryProps = {
category?: string | null category?: string | null
limit?: number limit?: number
enabled?: boolean
} }
export const getSuggestedUsersForSeeMoreQueryKeyRoot = export const getSuggestedUsersForSeeMoreQueryKeyRoot =
'unspecced-suggested-users-for-explore' 'unspecced-suggested-users-for-explore'
export const createGetSuggestedUsersForSeeMoreQueryKey = ( export const createGetSuggestedUsersForSeeMoreQueryKey = (props: {
props: QueryProps, category?: string | null
) => [getSuggestedUsersForSeeMoreQueryKeyRoot, props.category, props.limit] limit?: number
}) => [getSuggestedUsersForSeeMoreQueryKeyRoot, props.category, props.limit]
export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) { export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
const agent = useAgent() const agent = useAgent()
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
return useQuery({ return useQuery({
enabled: props.enabled ?? true,
staleTime: STALE.MINUTES.THREE, staleTime: STALE.MINUTES.THREE,
queryKey: createGetSuggestedUsersForSeeMoreQueryKey(props), queryKey: createGetSuggestedUsersForSeeMoreQueryKey({
category: props.category,
limit: props.limit,
}),
queryFn: async () => { queryFn: async () => {
const contentLangs = getContentLanguages().join(',') const contentLangs = getContentLanguages().join(',')
const userInterests = aggregateUserInterests(preferences) const userInterests = aggregateUserInterests(preferences)