Fall back to searching for users for non-english speakers
This commit is contained in:
@@ -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={
|
||||||
all: _(msg`For You`),
|
hideDefaultTab
|
||||||
...interestsDisplayNames,
|
? interestsDisplayNames
|
||||||
}}
|
: {
|
||||||
|
all: _(msg`For You`),
|
||||||
|
...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])
|
||||||
|
}
|
||||||
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user