diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index cfdc008b18..1827aeb0eb 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -18,6 +18,7 @@ import {logger} from '#/logger' import {type MetricEvents} from '#/logger/metrics' import {useLanguagePrefs} from '#/state/preferences/languages' import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {RQKEY_ROOT_PAGINATED as useActorSearchPaginatedQueryKeyRoot} from '#/state/queries/actor-search' import { type FeedPreviewItem, useFeedPreviews, @@ -29,10 +30,7 @@ import { createGetSuggestedFeedsQueryKey, useGetSuggestedFeedsQuery, } from '#/state/queries/trending/useGetSuggestedFeedsQuery' -import { - getSuggestedUsersQueryKeyRoot, - useGetSuggestedUsersQuery, -} from '#/state/queries/trending/useGetSuggestedUsersQuery' +import {getSuggestedUsersQueryKeyRoot} from '#/state/queries/trending/useGetSuggestedUsersQuery' import {createGetTrendsQueryKey} from '#/state/queries/trending/useGetTrendsQuery' import { createSuggestedStarterPacksQueryKey, @@ -45,6 +43,10 @@ import {ViewFullThread} from '#/view/com/posts/ViewFullThread' import {List} from '#/view/com/util/List' import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' +import { + popularInterests, + useInterestsDisplayNames, +} from '#/screens/Onboarding/state' import { StarterPackCard, StarterPackCardSkeleton, @@ -53,6 +55,7 @@ import {ExploreInterestsCard} from '#/screens/Search/modules/ExploreInterestsCar import {ExploreRecommendations} from '#/screens/Search/modules/ExploreRecommendations' import {ExploreTrendingTopics} from '#/screens/Search/modules/ExploreTrendingTopics' import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVideos' +import {useSuggestedUsers} from '#/screens/Search/util/useSuggestedUsers' import {atoms as a, native, platform, useTheme} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button} from '#/components/Button' @@ -67,6 +70,7 @@ import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Tre import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' import {Loader} from '#/components/Loader' import * as ProfileCard from '#/components/ProfileCard' +import {boostInterests} from '#/components/ProgressGuide/FollowDialog' import {SubtleHover} from '#/components/SubtleHover' import {Text} from '#/components/Typography' import * as ModuleHeader from './components/ModuleHeader' @@ -138,6 +142,7 @@ type ExploreScreenItems = metricsTag: MetricEvents['explore:module:searchButtonPress']['module'] tab: 'user' | 'profile' | 'feed' } + hideDefaultTab?: boolean } | { type: 'trendingTopics' @@ -216,18 +221,30 @@ export function Explore({ const gate = useGate() const guide = useProgressGuide('follow-10') const [selectedInterest, setSelectedInterest] = useState(null) + + /* + * Begin special language handling + */ const {contentLanguages} = useLanguagePrefs() - const userRequestsEnglish = useMemo(() => { + const useFullExperience = useMemo(() => { return bcp47Match.basicFilter('en', contentLanguages).length > 0 }, [contentLanguages]) + const personalizedInterests = preferences?.interests?.tags + const interestsDisplayNames = useInterestsDisplayNames() + const interests = Object.keys(interestsDisplayNames) + .sort(boostInterests(popularInterests)) + .sort(boostInterests(personalizedInterests)) const { data: suggestedUsers, isLoading: suggestedUsersIsLoading, error: suggestedUsersError, isRefetching: suggestedUsersIsRefetching, - } = useGetSuggestedUsersQuery({ - category: selectedInterest, + } = useSuggestedUsers({ + category: selectedInterest || (useFullExperience ? null : interests[0]), + search: !useFullExperience, }) + /* End special language handling */ + const { data: feeds, hasNextPage: hasNextFeedsPage, @@ -294,6 +311,9 @@ export function Explore({ await qc.resetQueries({ queryKey: [getSuggestedUsersQueryKeyRoot], }), + await qc.resetQueries({ + queryKey: [useActorSearchPaginatedQueryKeyRoot], + }), await qc.resetQueries({ queryKey: createGetSuggestedFeedsQueryKey(), }), @@ -342,6 +362,7 @@ export function Explore({ metricsTag: 'suggestedAccounts', tab: 'user', }, + hideDefaultTab: !useFullExperience, }) if (suggestedUsersIsLoading || suggestedUsersIsRefetching) { @@ -361,6 +382,7 @@ export function Explore({ let seen = new Set() const profileItems: ExploreScreenItems[] = [] for (const actor of suggestedUsers.actors) { + // checking for following still necessary if search data is used if (!seen.has(actor.did) && !actor.viewer?.following) { seen.add(actor.did) profileItems.push({ @@ -377,7 +399,7 @@ export function Explore({ key: 'profileEmpty', }) } else { - if (selectedInterest === null) { + if (selectedInterest === null && useFullExperience) { // First "For You" tab, only show 5 to keep screen short i.push(...profileItems.slice(0, 5)) } else { @@ -403,6 +425,7 @@ export function Explore({ suggestedUsersIsRefetching, suggestedUsersError, selectedInterest, + useFullExperience, ]) const suggestedFeedsModule = useMemo(() => { const i: ExploreScreenItems[] = [] @@ -574,7 +597,7 @@ export function Explore({ i.push(topBorder) i.push(...interestsNuxModule) - if (userRequestsEnglish) { + if (useFullExperience) { if (isNewUser) { i.push(...suggestedFollowsModule) i.push(...suggestedStarterPacksModule) @@ -611,7 +634,7 @@ export function Explore({ feedPreviewsModule, interestsNuxModule, gate, - userRequestsEnglish, + useFullExperience, ]) const renderItem = useCallback( @@ -655,6 +678,7 @@ export function Explore({ ) @@ -686,7 +710,13 @@ export function Explore({ return ( - No results for "{selectedInterest}". + {selectedInterest ? ( + + No results for "{interestsDisplayNames[selectedInterest]}". + + ) : ( + No results. + )} ) @@ -890,6 +920,7 @@ export function Explore({ focusSearchInput, moderationOpts, selectedInterest, + interestsDisplayNames, _, fetchNextPageFeedPreviews, ], diff --git a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx index 8d66dfbc18..f918771438 100644 --- a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx +++ b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx @@ -58,9 +58,11 @@ export function useLoadEnoughProfiles({ export function SuggestedAccountsTabBar({ selectedInterest, onSelectInterest, + hideDefaultTab, }: { selectedInterest: string | null onSelectInterest: (interest: string | null) => void + hideDefaultTab?: boolean }) { const {_} = useLingui() const interestsDisplayNames = useInterestsDisplayNames() @@ -72,8 +74,10 @@ export function SuggestedAccountsTabBar({ return ( { logger.metric( 'explore:suggestedAccounts:tabPressed', @@ -83,10 +87,14 @@ export function SuggestedAccountsTabBar({ onSelectInterest(tab === 'all' ? null : tab) }} hasSearchText={false} - interestsDisplayNames={{ - all: _(msg`For You`), - ...interestsDisplayNames, - }} + interestsDisplayNames={ + hideDefaultTab + ? interestsDisplayNames + : { + all: _(msg`For You`), + ...interestsDisplayNames, + } + } TabComponent={Tab} contentContainerStyle={[ { diff --git a/src/screens/Search/util/useSuggestedUsers.ts b/src/screens/Search/util/useSuggestedUsers.ts new file mode 100644 index 0000000000..aa29dad8c6 --- /dev/null +++ b/src/screens/Search/util/useSuggestedUsers.ts @@ -0,0 +1,56 @@ +import {useMemo} from 'react' + +import {useActorSearchPaginated} from '#/state/queries/actor-search' +import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery' +import {useInterestsDisplayNames} from '#/screens/Onboarding/state' + +/** + * Conditional hook, used in case a user is a non-english speaker, in which + * case we fall back to searching for users instead of our more curated set. + */ +export function useSuggestedUsers({ + category = null, + search = false, +}: { + category?: string | null + /** + * If true, we'll search for users using the translated value of `category`, + * based on the user's "app language setting + */ + search?: boolean +}) { + const interestsDisplayNames = useInterestsDisplayNames() + const curated = useGetSuggestedUsersQuery({ + enabled: !search, + category, + }) + const searched = useActorSearchPaginated({ + enabled: !!search, + // use user's app language translation for this value + query: category ? interestsDisplayNames[category] : '', + limit: 10, + }) + + return useMemo(() => { + if (search) { + return { + // we're not paginating right now + data: searched?.data + ? { + actors: searched.data.pages.flatMap(p => p.actors) ?? [], + } + : undefined, + isLoading: searched.isLoading, + error: searched.error, + isRefetching: searched.isRefetching, + } + } else { + return { + data: curated.data, + isLoading: curated.isLoading, + error: curated.error, + isRefetching: curated.isRefetching, + } + } + }, [curated, searched, search]) +} diff --git a/src/state/queries/actor-search.ts b/src/state/queries/actor-search.ts index 0b5de23037..5347ca0a15 100644 --- a/src/state/queries/actor-search.ts +++ b/src/state/queries/actor-search.ts @@ -17,7 +17,7 @@ import {useAgent} from '#/state/session' const RQKEY_ROOT = 'actor-search' export const RQKEY = (query: string) => [RQKEY_ROOT, query] -const RQKEY_ROOT_PAGINATED = `${RQKEY_ROOT}_paginated` +export const RQKEY_ROOT_PAGINATED = `${RQKEY_ROOT}_paginated` export const RQKEY_PAGINATED = (query: string, limit?: number) => [ RQKEY_ROOT_PAGINATED, query, diff --git a/src/state/queries/trending/useGetSuggestedUsersQuery.ts b/src/state/queries/trending/useGetSuggestedUsersQuery.ts index c8c3f00896..b6471bdcf9 100644 --- a/src/state/queries/trending/useGetSuggestedUsersQuery.ts +++ b/src/state/queries/trending/useGetSuggestedUsersQuery.ts @@ -13,12 +13,12 @@ import {STALE} from '#/state/queries' import {usePreferencesQuery} from '#/state/queries/preferences' import {useAgent} from '#/state/session' -export type QueryProps = {category?: string | null} +export type QueryProps = {category?: string | null; enabled?: boolean} export const getSuggestedUsersQueryKeyRoot = 'unspecced-suggested-users' export const createGetSuggestedUsersQueryKey = (props: QueryProps) => [ getSuggestedUsersQueryKeyRoot, - ...Object.values(props), + props.category, ] export function useGetSuggestedUsersQuery(props: QueryProps) { @@ -26,7 +26,7 @@ export function useGetSuggestedUsersQuery(props: QueryProps) { const {data: preferences} = usePreferencesQuery() return useQuery({ - enabled: !!preferences, + enabled: !!preferences && props.enabled, staleTime: STALE.MINUTES.THREE, queryKey: createGetSuggestedUsersQueryKey(props), queryFn: async () => {