diff options
author | Eric Bailey <git@esb.lol> | 2025-04-08 17:08:09 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-08 17:08:09 -0500 |
commit | c8568e3004831e76b29e0f710805cb4f1cfc7576 (patch) | |
tree | b09a3d51996a9ed6924520d150786a4b16aeb98f /src | |
parent | 09111ef272f04bbd6a1d7e43f072fdde06133669 (diff) | |
download | voidsky-c8568e3004831e76b29e0f710805cb4f1cfc7576.tar.zst |
[Explore] Reduced experience (#8160)
* Only show suggested users for non-english users * Fall back to searching for users for non-english speakers * Disable other queries if full experience is disabled * Bump package * If no content langs, use full exp
Diffstat (limited to 'src')
-rw-r--r-- | src/screens/Search/Explore.tsx | 106 | ||||
-rw-r--r-- | src/screens/Search/modules/ExploreSuggestedAccounts.tsx | 20 | ||||
-rw-r--r-- | src/screens/Search/util/useSuggestedUsers.ts | 56 | ||||
-rw-r--r-- | src/state/queries/actor-search.ts | 2 | ||||
-rw-r--r-- | src/state/queries/explore-feed-previews.tsx | 3 | ||||
-rw-r--r-- | src/state/queries/feed.ts | 22 | ||||
-rw-r--r-- | src/state/queries/trending/useGetSuggestedFeedsQuery.ts | 4 | ||||
-rw-r--r-- | src/state/queries/trending/useGetSuggestedUsersQuery.ts | 8 | ||||
-rw-r--r-- | src/state/queries/useSuggestedStarterPacksQuery.ts | 4 |
9 files changed, 168 insertions, 57 deletions
diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index 1236005a3..8050d7f73 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -8,13 +8,16 @@ import { import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' +import * as bcp47Match from 'bcp-47-match' import {useGate} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' 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, @@ -26,10 +29,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, @@ -43,6 +43,10 @@ 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, } from '#/screens/Search/components/StarterPackCard' @@ -50,6 +54,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' @@ -64,6 +69,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' @@ -135,6 +141,7 @@ type ExploreScreenItems = metricsTag: MetricEvents['explore:module:searchButtonPress']['module'] tab: 'user' | 'profile' | 'feed' } + hideDefaultTab?: boolean } | { type: 'trendingTopics' @@ -151,7 +158,7 @@ type ExploreScreenItems = | { type: 'profile' key: string - profile: AppBskyActorDefs.ProfileViewBasic + profile: AppBskyActorDefs.ProfileView recId?: number } | { @@ -212,14 +219,31 @@ export function Explore({ const gate = useGate() const guide = useProgressGuide('follow-10') const [selectedInterest, setSelectedInterest] = useState<string | null>(null) + + /* + * Begin special language handling + */ + const {contentLanguages} = useLanguagePrefs() + const useFullExperience = useMemo(() => { + if (contentLanguages.length === 0) return true + 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, @@ -227,7 +251,7 @@ export function Explore({ isFetchingNextPage: isFetchingNextFeedsPage, error: feedsError, fetchNextPage: fetchNextFeedsPage, - } = useGetPopularFeedsQuery({limit: 10}) + } = useGetPopularFeedsQuery({limit: 10, enabled: useFullExperience}) const interestsNux = useNux(Nux.ExploreInterestsCard) const showInterestsNux = interestsNux.status === 'ready' && !interestsNux.nux?.completed @@ -237,7 +261,7 @@ export function Explore({ isLoading: isLoadingSuggestedSPs, error: suggestedSPsError, isRefetching: isRefetchingSuggestedSPs, - } = useSuggestedStarterPacksQuery() + } = useSuggestedStarterPacksQuery({enabled: useFullExperience}) const isLoadingMoreFeeds = isFetchingNextFeedsPage && !isLoadingFeeds const [hasPressedLoadMoreFeeds, setHasPressedLoadMoreFeeds] = useState(false) @@ -260,7 +284,9 @@ export function Explore({ hasPressedLoadMoreFeeds, ]) - const {data: suggestedFeeds} = useGetSuggestedFeedsQuery() + const {data: suggestedFeeds} = useGetSuggestedFeedsQuery({ + enabled: useFullExperience, + }) const { data: feedPreviewSlices, query: { @@ -270,7 +296,7 @@ export function Explore({ hasNextPage: hasNextPageFeedPreviews, error: feedPreviewSlicesError, }, - } = useFeedPreviews(suggestedFeeds?.feeds ?? []) + } = useFeedPreviews(suggestedFeeds?.feeds ?? [], useFullExperience) const qc = useQueryClient() const [isPTR, setIsPTR] = useState(false) @@ -287,6 +313,9 @@ export function Explore({ queryKey: [getSuggestedUsersQueryKeyRoot], }), await qc.resetQueries({ + queryKey: [useActorSearchPaginatedQueryKeyRoot], + }), + await qc.resetQueries({ queryKey: createGetSuggestedFeedsQueryKey(), }), ]) @@ -334,6 +363,7 @@ export function Explore({ metricsTag: 'suggestedAccounts', tab: 'user', }, + hideDefaultTab: !useFullExperience, }) if (suggestedUsersIsLoading || suggestedUsersIsRefetching) { @@ -353,6 +383,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({ @@ -369,7 +400,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 { @@ -395,6 +426,7 @@ export function Explore({ suggestedUsersIsRefetching, suggestedUsersError, selectedInterest, + useFullExperience, ]) const suggestedFeedsModule = useMemo(() => { const i: ExploreScreenItems[] = [] @@ -565,26 +597,31 @@ export function Explore({ i.push(topBorder) i.push(...interestsNuxModule) - if (isNewUser) { - i.push(...suggestedFollowsModule) - i.push(...suggestedStarterPacksModule) - i.push({ - type: 'header', - key: 'trending-topics-header', - title: _(msg`Trending topics`), - icon: Graph, - bottomBorder: true, - }) - i.push(trendingTopicsModule) + + if (useFullExperience) { + if (isNewUser) { + i.push(...suggestedFollowsModule) + i.push(...suggestedStarterPacksModule) + i.push({ + type: 'header', + key: 'trending-topics-header', + title: _(msg`Trending topics`), + icon: Graph, + bottomBorder: true, + }) + i.push(trendingTopicsModule) + } else { + i.push(trendingTopicsModule) + i.push(...suggestedFollowsModule) + i.push(...suggestedStarterPacksModule) + } + if (gate('explore_show_suggested_feeds')) { + i.push(...suggestedFeedsModule) + } + i.push(...feedPreviewsModule) } else { - i.push(trendingTopicsModule) i.push(...suggestedFollowsModule) - i.push(...suggestedStarterPacksModule) } - if (gate('explore_show_suggested_feeds')) { - i.push(...suggestedFeedsModule) - } - i.push(...feedPreviewsModule) return i }, [ @@ -598,6 +635,7 @@ export function Explore({ feedPreviewsModule, interestsNuxModule, gate, + useFullExperience, ]) const renderItem = useCallback( @@ -641,6 +679,7 @@ export function Explore({ <SuggestedAccountsTabBar selectedInterest={selectedInterest} onSelectInterest={setSelectedInterest} + hideDefaultTab={item.hideDefaultTab} /> </View> ) @@ -672,7 +711,13 @@ export function Explore({ return ( <View style={[a.px_lg, a.pb_lg]}> <Admonition> - <Trans>No results for "{selectedInterest}".</Trans> + {selectedInterest ? ( + <Trans> + No results for "{interestsDisplayNames[selectedInterest]}". + </Trans> + ) : ( + <Trans>No results.</Trans> + )} </Admonition> </View> ) @@ -876,6 +921,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 8d66dfbc1..f91877143 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 ( <BlockDrawerGesture> <Tabs - interests={['all', ...interests]} - selectedInterest={selectedInterest || 'all'} + interests={hideDefaultTab ? interests : ['all', ...interests]} + selectedInterest={ + selectedInterest || (hideDefaultTab ? interests[0] : 'all') + } onSelectTab={tab => { 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 000000000..aa29dad8c --- /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 0b5de2303..5347ca0a1 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/explore-feed-previews.tsx b/src/state/queries/explore-feed-previews.tsx index 2aee8b6b3..4cd7336c0 100644 --- a/src/state/queries/explore-feed-previews.tsx +++ b/src/state/queries/explore-feed-previews.tsx @@ -120,6 +120,7 @@ export type FeedPreviewItem = export function useFeedPreviews( feedsMaybeWithDuplicates: AppBskyFeedDefs.GeneratorView[], + isEnabled: boolean = true, ) { const feeds = useMemo( () => @@ -135,7 +136,7 @@ export function useFeedPreviews( const {data: preferences} = usePreferencesQuery() const userInterests = aggregateUserInterests(preferences) const moderationOpts = useModerationOpts() - const enabled = feeds.length > 0 + const enabled = feeds.length > 0 && isEnabled const query = useInfiniteQuery({ enabled, diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index 5571c0949..89023e513 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -1,18 +1,18 @@ import {useCallback, useEffect, useMemo, useRef} from 'react' import { - AppBskyActorDefs, - AppBskyFeedDefs, - AppBskyGraphDefs, - AppBskyUnspeccedGetPopularFeedGenerators, + type AppBskyActorDefs, + type AppBskyFeedDefs, + type AppBskyGraphDefs, + type AppBskyUnspeccedGetPopularFeedGenerators, AtUri, moderateFeedGenerator, RichText, } from '@atproto/api' import { - InfiniteData, + type InfiniteData, keepPreviousData, - QueryClient, - QueryKey, + type QueryClient, + type QueryKey, useInfiniteQuery, useMutation, useQuery, @@ -28,7 +28,7 @@ import {usePreferencesQuery} from '#/state/queries/preferences' import {useAgent, useSession} from '#/state/session' import {router} from '#/routes' import {useModerationOpts} from '../preferences/moderation-opts' -import {FeedDescriptor} from './post-feed' +import {type FeedDescriptor} from './post-feed' import {precacheResolvedUri} from './resolve-uri' export type FeedSourceFeedInfo = { @@ -203,12 +203,12 @@ export const KNOWN_AUTHED_ONLY_FEEDS = [ 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why ] -type GetPopularFeedsOptions = {limit?: number} +type GetPopularFeedsOptions = {limit?: number; enabled?: boolean} export function createGetPopularFeedsQueryKey( options?: GetPopularFeedsOptions, ) { - return ['getPopularFeeds', options] + return ['getPopularFeeds', options?.limit] } export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { @@ -237,7 +237,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { QueryKey, string | undefined >({ - enabled: Boolean(moderationOpts), + enabled: Boolean(moderationOpts) && options?.enabled !== false, queryKey: createGetPopularFeedsQueryKey(options), queryFn: async ({pageParam}) => { const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({ diff --git a/src/state/queries/trending/useGetSuggestedFeedsQuery.ts b/src/state/queries/trending/useGetSuggestedFeedsQuery.ts index eef71f1ca..6eef80942 100644 --- a/src/state/queries/trending/useGetSuggestedFeedsQuery.ts +++ b/src/state/queries/trending/useGetSuggestedFeedsQuery.ts @@ -13,13 +13,13 @@ export const DEFAULT_LIMIT = 5 export const createGetSuggestedFeedsQueryKey = () => ['suggested-feeds'] -export function useGetSuggestedFeedsQuery() { +export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) { const agent = useAgent() const {data: preferences} = usePreferencesQuery() const savedFeeds = preferences?.savedFeeds return useQuery({ - enabled: !!preferences, + enabled: !!preferences && enabled !== false, staleTime: STALE.MINUTES.THREE, queryKey: createGetSuggestedFeedsQueryKey(), queryFn: async () => { diff --git a/src/state/queries/trending/useGetSuggestedUsersQuery.ts b/src/state/queries/trending/useGetSuggestedUsersQuery.ts index c8c3f0089..695e53f52 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 () => { @@ -52,7 +52,7 @@ export function useGetSuggestedUsersQuery(props: QueryProps) { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator<AppBskyActorDefs.ProfileViewBasic, void> { +): Generator<AppBskyActorDefs.ProfileView, void> { const responses = queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsers.OutputSchema>({ queryKey: [getSuggestedUsersQueryKeyRoot], diff --git a/src/state/queries/useSuggestedStarterPacksQuery.ts b/src/state/queries/useSuggestedStarterPacksQuery.ts index 3ec030ac0..cbf61b93a 100644 --- a/src/state/queries/useSuggestedStarterPacksQuery.ts +++ b/src/state/queries/useSuggestedStarterPacksQuery.ts @@ -13,13 +13,13 @@ export const createSuggestedStarterPacksQueryKey = () => [ 'suggested-starter-packs', ] -export function useSuggestedStarterPacksQuery() { +export function useSuggestedStarterPacksQuery({enabled}: {enabled?: boolean}) { const agent = useAgent() const {data: preferences} = usePreferencesQuery() const contentLangs = getContentLanguages().join(',') return useQuery({ - enabled: !!preferences, + enabled: !!preferences && enabled !== false, staleTime: STALE.MINUTES.THREE, queryKey: createSuggestedStarterPacksQueryKey(), async queryFn() { |