Fall back to searching for users for non-english speakers

This commit is contained in:
Eric Bailey
2025-04-08 14:24:04 -05:00
parent f3d979b316
commit 052ffcf1d9
5 changed files with 116 additions and 21 deletions
+42 -11
View File
@@ -18,6 +18,7 @@ import {logger} from '#/logger'
import {type MetricEvents} from '#/logger/metrics' import {type MetricEvents} from '#/logger/metrics'
import {useLanguagePrefs} from '#/state/preferences/languages' import {useLanguagePrefs} from '#/state/preferences/languages'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {RQKEY_ROOT_PAGINATED as useActorSearchPaginatedQueryKeyRoot} from '#/state/queries/actor-search'
import { import {
type FeedPreviewItem, type FeedPreviewItem,
useFeedPreviews, useFeedPreviews,
@@ -29,10 +30,7 @@ import {
createGetSuggestedFeedsQueryKey, createGetSuggestedFeedsQueryKey,
useGetSuggestedFeedsQuery, useGetSuggestedFeedsQuery,
} from '#/state/queries/trending/useGetSuggestedFeedsQuery' } from '#/state/queries/trending/useGetSuggestedFeedsQuery'
import { import {getSuggestedUsersQueryKeyRoot} from '#/state/queries/trending/useGetSuggestedUsersQuery'
getSuggestedUsersQueryKeyRoot,
useGetSuggestedUsersQuery,
} from '#/state/queries/trending/useGetSuggestedUsersQuery'
import {createGetTrendsQueryKey} from '#/state/queries/trending/useGetTrendsQuery' import {createGetTrendsQueryKey} from '#/state/queries/trending/useGetTrendsQuery'
import { import {
createSuggestedStarterPacksQueryKey, createSuggestedStarterPacksQueryKey,
@@ -45,6 +43,10 @@ import {ViewFullThread} from '#/view/com/posts/ViewFullThread'
import {List} from '#/view/com/util/List' import {List} from '#/view/com/util/List'
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
import {
popularInterests,
useInterestsDisplayNames,
} from '#/screens/Onboarding/state'
import { import {
StarterPackCard, StarterPackCard,
StarterPackCardSkeleton, StarterPackCardSkeleton,
@@ -53,6 +55,7 @@ import {ExploreInterestsCard} from '#/screens/Search/modules/ExploreInterestsCar
import {ExploreRecommendations} from '#/screens/Search/modules/ExploreRecommendations' import {ExploreRecommendations} from '#/screens/Search/modules/ExploreRecommendations'
import {ExploreTrendingTopics} from '#/screens/Search/modules/ExploreTrendingTopics' import {ExploreTrendingTopics} from '#/screens/Search/modules/ExploreTrendingTopics'
import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVideos' import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVideos'
import {useSuggestedUsers} from '#/screens/Search/util/useSuggestedUsers'
import {atoms as a, native, platform, useTheme} from '#/alf' import {atoms as a, native, platform, useTheme} from '#/alf'
import {Admonition} from '#/components/Admonition' import {Admonition} from '#/components/Admonition'
import {Button} from '#/components/Button' 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 {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import {boostInterests} from '#/components/ProgressGuide/FollowDialog'
import {SubtleHover} from '#/components/SubtleHover' import {SubtleHover} from '#/components/SubtleHover'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import * as ModuleHeader from './components/ModuleHeader' import * as ModuleHeader from './components/ModuleHeader'
@@ -138,6 +142,7 @@ type ExploreScreenItems =
metricsTag: MetricEvents['explore:module:searchButtonPress']['module'] metricsTag: MetricEvents['explore:module:searchButtonPress']['module']
tab: 'user' | 'profile' | 'feed' tab: 'user' | 'profile' | 'feed'
} }
hideDefaultTab?: boolean
} }
| { | {
type: 'trendingTopics' type: 'trendingTopics'
@@ -216,18 +221,30 @@ export function Explore({
const gate = useGate() const gate = useGate()
const guide = useProgressGuide('follow-10') const guide = useProgressGuide('follow-10')
const [selectedInterest, setSelectedInterest] = useState<string | null>(null) const [selectedInterest, setSelectedInterest] = useState<string | null>(null)
/*
* Begin special language handling
*/
const {contentLanguages} = useLanguagePrefs() const {contentLanguages} = useLanguagePrefs()
const userRequestsEnglish = useMemo(() => { const useFullExperience = useMemo(() => {
return bcp47Match.basicFilter('en', contentLanguages).length > 0 return bcp47Match.basicFilter('en', contentLanguages).length > 0
}, [contentLanguages]) }, [contentLanguages])
const personalizedInterests = preferences?.interests?.tags
const interestsDisplayNames = useInterestsDisplayNames()
const interests = Object.keys(interestsDisplayNames)
.sort(boostInterests(popularInterests))
.sort(boostInterests(personalizedInterests))
const { const {
data: suggestedUsers, data: suggestedUsers,
isLoading: suggestedUsersIsLoading, isLoading: suggestedUsersIsLoading,
error: suggestedUsersError, error: suggestedUsersError,
isRefetching: suggestedUsersIsRefetching, isRefetching: suggestedUsersIsRefetching,
} = useGetSuggestedUsersQuery({ } = useSuggestedUsers({
category: selectedInterest, category: selectedInterest || (useFullExperience ? null : interests[0]),
search: !useFullExperience,
}) })
/* End special language handling */
const { const {
data: feeds, data: feeds,
hasNextPage: hasNextFeedsPage, hasNextPage: hasNextFeedsPage,
@@ -294,6 +311,9 @@ export function Explore({
await qc.resetQueries({ await qc.resetQueries({
queryKey: [getSuggestedUsersQueryKeyRoot], queryKey: [getSuggestedUsersQueryKeyRoot],
}), }),
await qc.resetQueries({
queryKey: [useActorSearchPaginatedQueryKeyRoot],
}),
await qc.resetQueries({ await qc.resetQueries({
queryKey: createGetSuggestedFeedsQueryKey(), queryKey: createGetSuggestedFeedsQueryKey(),
}), }),
@@ -342,6 +362,7 @@ export function Explore({
metricsTag: 'suggestedAccounts', metricsTag: 'suggestedAccounts',
tab: 'user', tab: 'user',
}, },
hideDefaultTab: !useFullExperience,
}) })
if (suggestedUsersIsLoading || suggestedUsersIsRefetching) { if (suggestedUsersIsLoading || suggestedUsersIsRefetching) {
@@ -361,6 +382,7 @@ export function Explore({
let seen = new Set() let seen = new Set()
const profileItems: ExploreScreenItems[] = [] const profileItems: ExploreScreenItems[] = []
for (const actor of suggestedUsers.actors) { for (const actor of suggestedUsers.actors) {
// checking for following still necessary if search data is used
if (!seen.has(actor.did) && !actor.viewer?.following) { if (!seen.has(actor.did) && !actor.viewer?.following) {
seen.add(actor.did) seen.add(actor.did)
profileItems.push({ profileItems.push({
@@ -377,7 +399,7 @@ export function Explore({
key: 'profileEmpty', key: 'profileEmpty',
}) })
} else { } else {
if (selectedInterest === null) { if (selectedInterest === null && useFullExperience) {
// First "For You" tab, only show 5 to keep screen short // First "For You" tab, only show 5 to keep screen short
i.push(...profileItems.slice(0, 5)) i.push(...profileItems.slice(0, 5))
} else { } else {
@@ -403,6 +425,7 @@ export function Explore({
suggestedUsersIsRefetching, suggestedUsersIsRefetching,
suggestedUsersError, suggestedUsersError,
selectedInterest, selectedInterest,
useFullExperience,
]) ])
const suggestedFeedsModule = useMemo(() => { const suggestedFeedsModule = useMemo(() => {
const i: ExploreScreenItems[] = [] const i: ExploreScreenItems[] = []
@@ -574,7 +597,7 @@ export function Explore({
i.push(topBorder) i.push(topBorder)
i.push(...interestsNuxModule) i.push(...interestsNuxModule)
if (userRequestsEnglish) { if (useFullExperience) {
if (isNewUser) { if (isNewUser) {
i.push(...suggestedFollowsModule) i.push(...suggestedFollowsModule)
i.push(...suggestedStarterPacksModule) i.push(...suggestedStarterPacksModule)
@@ -611,7 +634,7 @@ export function Explore({
feedPreviewsModule, feedPreviewsModule,
interestsNuxModule, interestsNuxModule,
gate, gate,
userRequestsEnglish, useFullExperience,
]) ])
const renderItem = useCallback( const renderItem = useCallback(
@@ -655,6 +678,7 @@ export function Explore({
<SuggestedAccountsTabBar <SuggestedAccountsTabBar
selectedInterest={selectedInterest} selectedInterest={selectedInterest}
onSelectInterest={setSelectedInterest} onSelectInterest={setSelectedInterest}
hideDefaultTab={item.hideDefaultTab}
/> />
</View> </View>
) )
@@ -686,7 +710,13 @@ export function Explore({
return ( return (
<View style={[a.px_lg, a.pb_lg]}> <View style={[a.px_lg, a.pb_lg]}>
<Admonition> <Admonition>
<Trans>No results for "{selectedInterest}".</Trans> {selectedInterest ? (
<Trans>
No results for "{interestsDisplayNames[selectedInterest]}".
</Trans>
) : (
<Trans>No results.</Trans>
)}
</Admonition> </Admonition>
</View> </View>
) )
@@ -890,6 +920,7 @@ export function Explore({
focusSearchInput, focusSearchInput,
moderationOpts, moderationOpts,
selectedInterest, selectedInterest,
interestsDisplayNames,
_, _,
fetchNextPageFeedPreviews, fetchNextPageFeedPreviews,
], ],
@@ -58,9 +58,11 @@ export function useLoadEnoughProfiles({
export function SuggestedAccountsTabBar({ export function SuggestedAccountsTabBar({
selectedInterest, selectedInterest,
onSelectInterest, onSelectInterest,
hideDefaultTab,
}: { }: {
selectedInterest: string | null selectedInterest: string | null
onSelectInterest: (interest: string | null) => void onSelectInterest: (interest: string | null) => void
hideDefaultTab?: boolean
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const interestsDisplayNames = useInterestsDisplayNames() const interestsDisplayNames = useInterestsDisplayNames()
@@ -72,8 +74,10 @@ export function SuggestedAccountsTabBar({
return ( return (
<BlockDrawerGesture> <BlockDrawerGesture>
<Tabs <Tabs
interests={['all', ...interests]} interests={hideDefaultTab ? interests : ['all', ...interests]}
selectedInterest={selectedInterest || 'all'} selectedInterest={
selectedInterest || (hideDefaultTab ? interests[0] : 'all')
}
onSelectTab={tab => { onSelectTab={tab => {
logger.metric( logger.metric(
'explore:suggestedAccounts:tabPressed', 'explore:suggestedAccounts:tabPressed',
@@ -83,10 +87,14 @@ export function SuggestedAccountsTabBar({
onSelectInterest(tab === 'all' ? null : tab) onSelectInterest(tab === 'all' ? null : tab)
}} }}
hasSearchText={false} hasSearchText={false}
interestsDisplayNames={{ interestsDisplayNames={
hideDefaultTab
? interestsDisplayNames
: {
all: _(msg`For You`), all: _(msg`For You`),
...interestsDisplayNames, ...interestsDisplayNames,
}} }
}
TabComponent={Tab} TabComponent={Tab}
contentContainerStyle={[ contentContainerStyle={[
{ {
@@ -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])
}
+1 -1
View File
@@ -17,7 +17,7 @@ import {useAgent} from '#/state/session'
const RQKEY_ROOT = 'actor-search' const RQKEY_ROOT = 'actor-search'
export const RQKEY = (query: string) => [RQKEY_ROOT, query] 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) => [ export const RQKEY_PAGINATED = (query: string, limit?: number) => [
RQKEY_ROOT_PAGINATED, RQKEY_ROOT_PAGINATED,
query, query,
@@ -13,12 +13,12 @@ import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session' 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 getSuggestedUsersQueryKeyRoot = 'unspecced-suggested-users'
export const createGetSuggestedUsersQueryKey = (props: QueryProps) => [ export const createGetSuggestedUsersQueryKey = (props: QueryProps) => [
getSuggestedUsersQueryKeyRoot, getSuggestedUsersQueryKeyRoot,
...Object.values(props), props.category,
] ]
export function useGetSuggestedUsersQuery(props: QueryProps) { export function useGetSuggestedUsersQuery(props: QueryProps) {
@@ -26,7 +26,7 @@ export function useGetSuggestedUsersQuery(props: QueryProps) {
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
return useQuery({ return useQuery({
enabled: !!preferences, enabled: !!preferences && props.enabled,
staleTime: STALE.MINUTES.THREE, staleTime: STALE.MINUTES.THREE,
queryKey: createGetSuggestedUsersQueryKey(props), queryKey: createGetSuggestedUsersQueryKey(props),
queryFn: async () => { queryFn: async () => {