Overfetch trending topics before filtering (#11477)

This commit is contained in:
DS Boyce
2026-08-24 11:39:46 -07:00
committed by GitHub
parent 6009e61ae9
commit 6f46927c28
5 changed files with 14 additions and 169 deletions
-154
View File
@@ -1,154 +0,0 @@
import {useCallback} from 'react'
import {ScrollView, View} from 'react-native'
import {useLingui} from '@lingui/react/macro'
import {
useTrendingSettings,
useTrendingSettingsApi,
} from '#/state/preferences/trending'
import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery'
import {useTrendingConfig} from '#/state/service-config'
import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
import {atoms as a, useGutters, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending'
import * as Prompt from '#/components/Prompt'
import {TrendingTopicLink} from '#/components/TrendingTopics'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
const TRENDING_LIMIT = 14
export function TrendingInterstitial() {
const {enabled} = useTrendingConfig()
const {trendingDisabled} = useTrendingSettings()
return enabled && !trendingDisabled ? <Inner /> : null
}
export function Inner() {
const t = useTheme()
const {t: l} = useLingui()
const ax = useAnalytics()
const gutters = useGutters([0, 'base', 0, 'base'])
const trendingPrompt = Prompt.usePromptControl()
const {setTrendingDisabled} = useTrendingSettingsApi()
const {
data: trending,
error,
isLoading,
} = useGetTrendsQuery({
limit: TRENDING_LIMIT,
refetchOnWindowFocus: true,
})
const noTopics = !isLoading && !error && !trending?.trends?.length
const onConfirmHide = useCallback(() => {
ax.metric('trendingTopics:hide', {context: 'interstitial'})
setTrendingDisabled(true)
}, [ax, setTrendingDisabled])
return error || noTopics ? null : (
<View style={[t.atoms.border_contrast_low, a.border_t, a.border_b]}>
<BlockDrawerGesture>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
decelerationRate="fast">
<View style={[gutters, a.flex_row, a.align_center, a.gap_lg]}>
<View style={{paddingLeft: 4, paddingRight: 2}}>
<Graph size="sm" />
</View>
{isLoading ? (
<View style={[a.py_lg, a.flex_row, a.gap_lg, a.align_center]}>
<LoadingPlaceholder
width={80}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<LoadingPlaceholder
width={50}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<LoadingPlaceholder
width={120}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<LoadingPlaceholder
width={30}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<LoadingPlaceholder
width={180}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<Text
style={[
t.atoms.text_contrast_medium,
a.text_sm,
a.font_semi_bold,
]}>
{' '}
</Text>
</View>
) : !trending?.trends ? null : (
<>
{trending.trends.map((topic, index) => {
const rank = index + 1
return (
<TrendingTopicLink
key={topic.link}
topic={topic}
metricContext="interstitial"
rank={rank}
recId={trending.recId}
onPress={() => {
ax.metric('trendingTopic:click', {
context: 'interstitial',
rank,
recId: trending.recId,
})
}}>
<View style={[a.py_lg]}>
<Text
style={[
t.atoms.text_contrast_medium,
a.text_sm,
a.font_semi_bold,
]}>
{topic.topic}
</Text>
</View>
</TrendingTopicLink>
)
})}
<Button
label={l`Hide trending topics`}
size="tiny"
variant="ghost"
color="secondary"
shape="round"
onPress={() => trendingPrompt.open()}>
<ButtonIcon icon={X} />
</Button>
</>
)}
</View>
</ScrollView>
</BlockDrawerGesture>
<Prompt.Basic
control={trendingPrompt}
title={l`Hide trending topics?`}
description={l`You can update this later from your settings.`}
confirmButtonCta={l`Hide`}
onConfirm={onConfirmHide}
/>
</View>
)
}
@@ -11,6 +11,7 @@ import {
useTrendingSettingsApi,
} from '#/state/preferences/trending'
import {
DEFAULT_FETCH_LIMIT,
DEFAULT_LIMIT,
useGetTrendsQuery,
} from '#/state/queries/trending/useGetTrendsQuery'
@@ -57,7 +58,10 @@ function Inner() {
error,
isLoading,
isRefetching,
} = useGetTrendsQuery({limit: topicCount})
} = useGetTrendsQuery({
fetchLimit: Math.min(topicCount * 2, DEFAULT_FETCH_LIMIT),
limit: topicCount,
})
const noTopics = !isLoading && !error && !trending?.trends?.length
const showLoading = isLoading || isRefetching
@@ -14,8 +14,10 @@ import {useAppviewClient} from '#/state/session'
import {app} from '#/lexicons'
export const DEFAULT_LIMIT = 5
export const DEFAULT_FETCH_LIMIT = 20
type QueryProps = {
fetchLimit?: number
limit?: number
refetchOnWindowFocus?: boolean
}
@@ -29,12 +31,13 @@ function dedupe<T extends {link: string}>(trends: T[]): T[] {
})
}
export const createGetTrendsQueryKey = (limit?: number) =>
limit === undefined ? ['trends'] : ['trends', {limit}]
export const createGetTrendsQueryKey = (fetchLimit?: number) =>
fetchLimit === undefined ? ['trends'] : ['trends', {limit: fetchLimit}]
export function useGetTrendsQuery(props: QueryProps = {}) {
const client = useAppviewClient()
const {data: preferences} = usePreferencesQuery()
const fetchLimit = props.fetchLimit ?? DEFAULT_FETCH_LIMIT
const limit = props.limit ?? DEFAULT_LIMIT
const mutedWords = useMemo(() => {
return preferences?.moderationPrefs?.mutedWords || []
@@ -44,13 +47,13 @@ export function useGetTrendsQuery(props: QueryProps = {}) {
enabled: !!preferences,
refetchOnWindowFocus: props.refetchOnWindowFocus,
staleTime: STALE.MINUTES.THREE,
queryKey: createGetTrendsQueryKey(limit),
queryKey: createGetTrendsQueryKey(fetchLimit),
queryFn: async () => {
const contentLangs = getContentLanguages().join(',')
const data = await client.call(
app.bsky.unspecced.getTrends,
{
limit,
limit: fetchLimit,
},
{
headers: {
@@ -75,10 +78,10 @@ export function useGetTrendsQuery(props: QueryProps = {}) {
text: `${t.topic} ${t.displayName} ${t.category}`,
})
}),
),
).slice(0, limit),
}
},
[mutedWords],
[limit, mutedWords],
),
})
}
-7
View File
@@ -62,7 +62,6 @@ import {
PostFeedVideoGridRowPlaceholder,
} from '#/components/feeds/PostFeedVideoGridRow'
import {FeedTrendingTopicsInterstitial} from '#/components/interstitials/FeedTrendingTopics'
import {TrendingInterstitial} from '#/components/interstitials/Trending'
import {TrendingVideos as TrendingVideosInterstitial} from '#/components/interstitials/TrendingVideos'
import {isStandardSiteEmbed} from '#/components/Post/Embed/StandardSiteEmbed/utils'
import {RichText} from '#/components/RichText'
@@ -146,10 +145,6 @@ type FeedRow =
type: 'interstitialProgressGuide'
key: string
}
| {
type: 'interstitialTrending'
key: string
}
| {
type: 'interstitialFeedTrendingTopics'
key: string
@@ -853,8 +848,6 @@ let PostFeed = ({
return <ProgressGuide />
} else if (row.type === 'ageAssuranceBanner') {
return <AgeAssuranceDismissibleFeedBanner />
} else if (row.type === 'interstitialTrending') {
return <TrendingInterstitial />
} else if (row.type === 'interstitialFeedTrendingTopics') {
return (
<FeedTrendingTopicsInterstitial feedSliceIndex={row.feedSliceIndex} />
@@ -46,7 +46,6 @@ function Inner() {
error,
isLoading,
} = useGetTrendsQuery({
limit: DEFAULT_LIMIT,
refetchOnWindowFocus: true,
})
const noTopics = !isLoading && !error && !trending?.trends?.length