Pass recId with trending topic click metrics (#11232)
This commit is contained in:
@@ -510,7 +510,7 @@ export type Events = {
|
|||||||
| 'ProgressGuide'
|
| 'ProgressGuide'
|
||||||
location: 'Card' | 'Profile' | 'FollowAll'
|
location: 'Card' | 'Profile' | 'FollowAll'
|
||||||
recSource?: 'Search'
|
recSource?: 'Search'
|
||||||
recId?: number | string
|
recId?: string
|
||||||
position: number
|
position: number
|
||||||
suggestedDid: string
|
suggestedDid: string
|
||||||
category: string | null
|
category: string | null
|
||||||
@@ -523,7 +523,7 @@ export type Events = {
|
|||||||
| 'ProfileHeader'
|
| 'ProfileHeader'
|
||||||
| 'Onboarding'
|
| 'Onboarding'
|
||||||
| 'SeeMoreSuggestedUsers'
|
| 'SeeMoreSuggestedUsers'
|
||||||
recId?: number | string
|
recId?: string
|
||||||
position: number
|
position: number
|
||||||
suggestedDid: string
|
suggestedDid: string
|
||||||
category: string | null
|
category: string | null
|
||||||
@@ -538,7 +538,7 @@ export type Events = {
|
|||||||
| 'SeeMoreSuggestedUsers'
|
| 'SeeMoreSuggestedUsers'
|
||||||
| 'ProgressGuide'
|
| 'ProgressGuide'
|
||||||
recSource?: 'Search'
|
recSource?: 'Search'
|
||||||
recId?: number | string
|
recId?: string
|
||||||
position: number
|
position: number
|
||||||
suggestedDid: string
|
suggestedDid: string
|
||||||
category: string | null
|
category: string | null
|
||||||
@@ -550,11 +550,11 @@ export type Events = {
|
|||||||
| 'ProfileInterstitial'
|
| 'ProfileInterstitial'
|
||||||
| 'ProfileHeader'
|
| 'ProfileHeader'
|
||||||
| 'Onboarding'
|
| 'Onboarding'
|
||||||
recId?: number | string
|
recId?: string
|
||||||
}
|
}
|
||||||
'suggestedUser:dismiss': {
|
'suggestedUser:dismiss': {
|
||||||
logContext: 'DiscoverInterstitial' | 'ProfileInterstitial' | 'ProfileHeader'
|
logContext: 'DiscoverInterstitial' | 'ProfileInterstitial' | 'ProfileHeader'
|
||||||
recId?: number | string
|
recId?: string
|
||||||
position: number
|
position: number
|
||||||
suggestedDid: string
|
suggestedDid: string
|
||||||
}
|
}
|
||||||
@@ -743,9 +743,7 @@ export type Events = {
|
|||||||
}
|
}
|
||||||
'trendingTopic:click': {
|
'trendingTopic:click': {
|
||||||
context: 'sidebar' | 'interstitial' | 'explore'
|
context: 'sidebar' | 'interstitial' | 'explore'
|
||||||
}
|
recId?: string
|
||||||
'recommendedTopic:click': {
|
|
||||||
context: 'explore'
|
|
||||||
}
|
}
|
||||||
'trendingVideos:show': {
|
'trendingVideos:show': {
|
||||||
context: 'settings'
|
context: 'settings'
|
||||||
|
|||||||
@@ -1,143 +1,20 @@
|
|||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
import {View} from 'react-native'
|
import {type AppBskyUnspeccedDefs, type AtUri} from '@atproto/api'
|
||||||
import {type AtUri} from '@atproto/api'
|
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||||
// import {makeProfileLink} from '#/lib/routes/links'
|
// import {makeProfileLink} from '#/lib/routes/links'
|
||||||
// import {feedUriToHref} from '#/lib/strings/url-helpers'
|
// import {feedUriToHref} from '#/lib/strings/url-helpers'
|
||||||
// import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag'
|
import {native} from '#/alf'
|
||||||
// import {CloseQuote_Filled_Stroke2_Corner0_Rounded as Quote} from '#/components/icons/Quote'
|
|
||||||
// import {UserAvatar} from '#/view/com/util/UserAvatar'
|
|
||||||
import {type TrendingTopic} from '#/state/queries/trending/useTrendingTopics'
|
|
||||||
import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf'
|
|
||||||
import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack'
|
|
||||||
import {Link as InternalLink, type LinkProps} from '#/components/Link'
|
import {Link as InternalLink, type LinkProps} from '#/components/Link'
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
|
|
||||||
export function TrendingTopic({
|
|
||||||
topic: raw,
|
|
||||||
size,
|
|
||||||
style,
|
|
||||||
hovered,
|
|
||||||
}: {
|
|
||||||
topic: TrendingTopic
|
|
||||||
size?: 'large' | 'small'
|
|
||||||
hovered?: boolean
|
|
||||||
} & ViewStyleProp) {
|
|
||||||
const topic = useTopic(raw)
|
|
||||||
|
|
||||||
const isSmall = size === 'small'
|
|
||||||
const hasIcon = topic.type === 'starter-pack' && !isSmall
|
|
||||||
const iconSize = 20
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
isSmall
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
paddingVertical: 2,
|
|
||||||
paddingHorizontal: 4,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [a.py_xs, a.px_sm],
|
|
||||||
hasIcon && {gap: 6},
|
|
||||||
style,
|
|
||||||
]}>
|
|
||||||
{hasIcon && topic.type === 'starter-pack' && (
|
|
||||||
<StarterPackIcon
|
|
||||||
gradient="sky"
|
|
||||||
width={iconSize}
|
|
||||||
style={{marginLeft: -3, marginVertical: -1}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/*
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
a.rounded_full,
|
|
||||||
a.overflow_hidden,
|
|
||||||
{
|
|
||||||
width: iconSize,
|
|
||||||
height: iconSize,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
{topic.type === 'tag' ? (
|
|
||||||
<Hashtag width={iconSize} />
|
|
||||||
) : topic.type === 'topic' ? (
|
|
||||||
<Quote width={iconSize - 2} />
|
|
||||||
) : topic.type === 'feed' ? (
|
|
||||||
<UserAvatar
|
|
||||||
type="user"
|
|
||||||
size={aviSize}
|
|
||||||
avatar=""
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<UserAvatar
|
|
||||||
type="user"
|
|
||||||
size={aviSize}
|
|
||||||
avatar=""
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
a.font_semi_bold,
|
|
||||||
a.leading_tight,
|
|
||||||
isSmall ? [a.text_sm] : [a.text_md, {paddingBottom: 1}],
|
|
||||||
hovered && {textDecorationLine: 'underline'},
|
|
||||||
]}
|
|
||||||
numberOfLines={1}>
|
|
||||||
{topic.displayName}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TrendingTopicSkeleton({
|
|
||||||
size = 'large',
|
|
||||||
index = 0,
|
|
||||||
}: {
|
|
||||||
size?: 'large' | 'small'
|
|
||||||
index?: number
|
|
||||||
}) {
|
|
||||||
const t = useTheme()
|
|
||||||
const isSmall = size === 'small'
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.rounded_full,
|
|
||||||
a.border,
|
|
||||||
t.atoms.border_contrast_medium,
|
|
||||||
t.atoms.bg_contrast_25,
|
|
||||||
isSmall
|
|
||||||
? {
|
|
||||||
width: index % 2 === 0 ? 75 : 90,
|
|
||||||
height: 27,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
width: index % 2 === 0 ? 90 : 110,
|
|
||||||
height: 36,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TrendingTopicLink({
|
export function TrendingTopicLink({
|
||||||
topic: raw,
|
topic: raw,
|
||||||
children,
|
children,
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: {
|
||||||
topic: TrendingTopic
|
topic: AppBskyUnspeccedDefs.TrendView
|
||||||
} & Omit<LinkProps, 'to' | 'label'>) {
|
} & Omit<LinkProps, 'to' | 'label'>) {
|
||||||
const topic = useTopic(raw)
|
const topic = useTopic(raw)
|
||||||
|
|
||||||
@@ -168,7 +45,9 @@ type ParsedTrendingTopic =
|
|||||||
uri: AtUri
|
uri: AtUri
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTopic(raw: TrendingTopic): ParsedTrendingTopic {
|
export function useTopic(
|
||||||
|
raw: AppBskyUnspeccedDefs.TrendView,
|
||||||
|
): ParsedTrendingTopic {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const {topic: displayName, link} = raw
|
const {topic: displayName, link} = raw
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
useTrendingSettings,
|
useTrendingSettings,
|
||||||
useTrendingSettingsApi,
|
useTrendingSettingsApi,
|
||||||
} from '#/state/preferences/trending'
|
} from '#/state/preferences/trending'
|
||||||
import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics'
|
import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery'
|
||||||
import {useTrendingConfig} from '#/state/service-config'
|
import {useTrendingConfig} from '#/state/service-config'
|
||||||
import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||||
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
|
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
|
||||||
@@ -20,6 +20,8 @@ import {TrendingTopicLink} from '#/components/TrendingTopics'
|
|||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
|
|
||||||
|
const TRENDING_LIMIT = 14
|
||||||
|
|
||||||
export function TrendingInterstitial() {
|
export function TrendingInterstitial() {
|
||||||
const {enabled} = useTrendingConfig()
|
const {enabled} = useTrendingConfig()
|
||||||
const {trendingDisabled} = useTrendingSettings()
|
const {trendingDisabled} = useTrendingSettings()
|
||||||
@@ -33,8 +35,15 @@ export function Inner() {
|
|||||||
const gutters = useGutters([0, 'base', 0, 'base'])
|
const gutters = useGutters([0, 'base', 0, 'base'])
|
||||||
const trendingPrompt = Prompt.usePromptControl()
|
const trendingPrompt = Prompt.usePromptControl()
|
||||||
const {setTrendingDisabled} = useTrendingSettingsApi()
|
const {setTrendingDisabled} = useTrendingSettingsApi()
|
||||||
const {data: trending, error, isLoading} = useTrendingTopics()
|
const {
|
||||||
const noTopics = !isLoading && !error && !trending?.topics?.length
|
data: trending,
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
} = useGetTrendsQuery({
|
||||||
|
limit: TRENDING_LIMIT,
|
||||||
|
refetchOnWindowFocus: true,
|
||||||
|
})
|
||||||
|
const noTopics = !isLoading && !error && !trending?.trends?.length
|
||||||
|
|
||||||
const onConfirmHide = useCallback(() => {
|
const onConfirmHide = useCallback(() => {
|
||||||
ax.metric('trendingTopics:hide', {context: 'interstitial'})
|
ax.metric('trendingTopics:hide', {context: 'interstitial'})
|
||||||
@@ -88,15 +97,16 @@ export function Inner() {
|
|||||||
{' '}
|
{' '}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : !trending?.topics ? null : (
|
) : !trending?.trends ? null : (
|
||||||
<>
|
<>
|
||||||
{trending.topics.map(topic => (
|
{trending.trends.map(topic => (
|
||||||
<TrendingTopicLink
|
<TrendingTopicLink
|
||||||
key={topic.link}
|
key={topic.link}
|
||||||
topic={topic}
|
topic={topic}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
ax.metric('trendingTopic:click', {
|
ax.metric('trendingTopic:click', {
|
||||||
context: 'interstitial',
|
context: 'interstitial',
|
||||||
|
recId: trending.recId,
|
||||||
})
|
})
|
||||||
}}>
|
}}>
|
||||||
<View style={[a.py_lg]}>
|
<View style={[a.py_lg]}>
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ function SuggestedProfileCard({
|
|||||||
category: string | null
|
category: string | null
|
||||||
onSeen: (did: string, position: number) => void
|
onSeen: (did: string, position: number) => void
|
||||||
recSource?: 'Search'
|
recSource?: 'Search'
|
||||||
recId?: number | string
|
recId?: string
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import {
|
|||||||
StarterPackCardSkeleton,
|
StarterPackCardSkeleton,
|
||||||
} from '#/screens/Search/components/StarterPackCard'
|
} from '#/screens/Search/components/StarterPackCard'
|
||||||
import {ExploreInterestsCard} from '#/screens/Search/modules/ExploreInterestsCard'
|
import {ExploreInterestsCard} from '#/screens/Search/modules/ExploreInterestsCard'
|
||||||
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 {atoms as a, native, platform, useTheme} from '#/alf'
|
import {atoms as a, native, platform, useTheme} from '#/alf'
|
||||||
@@ -154,10 +153,6 @@ type ExploreScreenItems =
|
|||||||
type: 'trendingVideos'
|
type: 'trendingVideos'
|
||||||
key: string
|
key: string
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
type: 'recommendations'
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
type: 'profile'
|
type: 'profile'
|
||||||
key: string
|
key: string
|
||||||
@@ -807,9 +802,6 @@ export function Explore({
|
|||||||
case 'trendingVideos': {
|
case 'trendingVideos': {
|
||||||
return <ExploreTrendingVideos />
|
return <ExploreTrendingVideos />
|
||||||
}
|
}
|
||||||
case 'recommendations': {
|
|
||||||
return <ExploreRecommendations />
|
|
||||||
}
|
|
||||||
case 'profile': {
|
case 'profile': {
|
||||||
return (
|
return (
|
||||||
<SuggestedProfileCard
|
<SuggestedProfileCard
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
import {View} from 'react-native'
|
|
||||||
import {type AppBskyUnspeccedDefs} from '@atproto/api'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
|
|
||||||
import {
|
|
||||||
DEFAULT_LIMIT as RECOMMENDATIONS_COUNT,
|
|
||||||
useTrendingTopics,
|
|
||||||
} from '#/state/queries/trending/useTrendingTopics'
|
|
||||||
import {useTrendingConfig} from '#/state/service-config'
|
|
||||||
import {atoms as a, useGutters, useTheme} from '#/alf'
|
|
||||||
import {Hashtag_Stroke2_Corner0_Rounded} from '#/components/icons/Hashtag'
|
|
||||||
import {
|
|
||||||
TrendingTopic,
|
|
||||||
TrendingTopicLink,
|
|
||||||
TrendingTopicSkeleton,
|
|
||||||
} from '#/components/TrendingTopics'
|
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
import {useAnalytics} from '#/analytics'
|
|
||||||
import {IS_WEB} from '#/env'
|
|
||||||
|
|
||||||
// Note: This module is not currently used and may be removed in the future.
|
|
||||||
|
|
||||||
export function ExploreRecommendations() {
|
|
||||||
const {enabled} = useTrendingConfig()
|
|
||||||
return enabled ? <Inner /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
function Inner() {
|
|
||||||
const t = useTheme()
|
|
||||||
const ax = useAnalytics()
|
|
||||||
const gutters = useGutters([0, 'compact'])
|
|
||||||
const {data: trending, error, isLoading} = useTrendingTopics()
|
|
||||||
const noRecs = !isLoading && !error && !trending?.suggested?.length
|
|
||||||
const allFeeds = trending?.suggested && isAllFeeds(trending.suggested)
|
|
||||||
|
|
||||||
return error || noRecs ? null : (
|
|
||||||
<>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
IS_WEB
|
|
||||||
? [a.px_lg, a.py_lg, a.pt_2xl, a.gap_md]
|
|
||||||
: [a.p_lg, a.pt_2xl, a.gap_md],
|
|
||||||
a.border_b,
|
|
||||||
t.atoms.border_contrast_low,
|
|
||||||
]}>
|
|
||||||
<View style={[a.flex_1, a.gap_sm]}>
|
|
||||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
|
||||||
<Hashtag_Stroke2_Corner0_Rounded
|
|
||||||
size="lg"
|
|
||||||
fill={t.palette.primary_500}
|
|
||||||
style={{marginLeft: -2}}
|
|
||||||
/>
|
|
||||||
<Text style={[a.text_2xl, a.font_bold, t.atoms.text]}>
|
|
||||||
<Trans>Recommended</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
{!allFeeds ? (
|
|
||||||
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
|
|
||||||
<Trans>
|
|
||||||
Content from across the network we think you might like.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
|
|
||||||
<Trans>Feeds we think you might like.</Trans>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.pt_md, a.pb_lg]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
a.justify_start,
|
|
||||||
a.flex_wrap,
|
|
||||||
{rowGap: 8, columnGap: 6},
|
|
||||||
gutters,
|
|
||||||
]}>
|
|
||||||
{isLoading ? (
|
|
||||||
Array(RECOMMENDATIONS_COUNT)
|
|
||||||
.fill(0)
|
|
||||||
.map((_, i) => <TrendingTopicSkeleton key={i} index={i} />)
|
|
||||||
) : !trending?.suggested ? null : (
|
|
||||||
<>
|
|
||||||
{trending.suggested.map(topic => (
|
|
||||||
<TrendingTopicLink
|
|
||||||
key={topic.link}
|
|
||||||
topic={topic}
|
|
||||||
onPress={() => {
|
|
||||||
ax.metric('recommendedTopic:click', {context: 'explore'})
|
|
||||||
}}>
|
|
||||||
{({hovered}) => (
|
|
||||||
<TrendingTopic
|
|
||||||
topic={topic}
|
|
||||||
style={[
|
|
||||||
hovered && [
|
|
||||||
t.atoms.border_contrast_high,
|
|
||||||
t.atoms.bg_contrast_25,
|
|
||||||
],
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TrendingTopicLink>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAllFeeds(topics: AppBskyUnspeccedDefs.TrendingTopic[]) {
|
|
||||||
return topics.every(topic => {
|
|
||||||
const segments = topic.link.split('/').slice(1)
|
|
||||||
return segments[0] === 'profile' && segments[2] === 'feed'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -45,7 +45,10 @@ function Inner() {
|
|||||||
trend={trend}
|
trend={trend}
|
||||||
rank={index + 1}
|
rank={index + 1}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
ax.metric('trendingTopic:click', {context: 'explore'})
|
ax.metric('trendingTopic:click', {
|
||||||
|
context: 'explore',
|
||||||
|
recId: trending.recId,
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
aggregateUserInterests,
|
aggregateUserInterests,
|
||||||
createBskyTopicsHeader,
|
createBskyTopicsHeader,
|
||||||
} from '#/lib/api/feed/utils'
|
} from '#/lib/api/feed/utils'
|
||||||
|
import {logger} from '#/logger'
|
||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
@@ -13,24 +14,43 @@ import {useAgent} from '#/state/session'
|
|||||||
|
|
||||||
export const DEFAULT_LIMIT = 5
|
export const DEFAULT_LIMIT = 5
|
||||||
|
|
||||||
export const createGetTrendsQueryKey = () => ['trends']
|
type QueryProps = {
|
||||||
|
limit?: number
|
||||||
|
refetchOnWindowFocus?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export function useGetTrendsQuery() {
|
function dedupe<T extends {link: string}>(trends: T[]): T[] {
|
||||||
|
const seen = new Set<string>()
|
||||||
|
return trends.filter(trend => {
|
||||||
|
if (seen.has(trend.link)) return false
|
||||||
|
seen.add(trend.link)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createGetTrendsQueryKey = (props: QueryProps = {}) => [
|
||||||
|
'trends',
|
||||||
|
props.limit ?? DEFAULT_LIMIT,
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useGetTrendsQuery(props: QueryProps = {}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
const limit = props.limit ?? DEFAULT_LIMIT
|
||||||
const mutedWords = useMemo(() => {
|
const mutedWords = useMemo(() => {
|
||||||
return preferences?.moderationPrefs?.mutedWords || []
|
return preferences?.moderationPrefs?.mutedWords || []
|
||||||
}, [preferences?.moderationPrefs])
|
}, [preferences?.moderationPrefs])
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
enabled: !!preferences,
|
enabled: !!preferences,
|
||||||
|
refetchOnWindowFocus: props.refetchOnWindowFocus,
|
||||||
staleTime: STALE.MINUTES.THREE,
|
staleTime: STALE.MINUTES.THREE,
|
||||||
queryKey: createGetTrendsQueryKey(),
|
queryKey: createGetTrendsQueryKey({limit}),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
const {data} = await agent.app.bsky.unspecced.getTrends(
|
const {data} = await agent.app.bsky.unspecced.getTrends(
|
||||||
{
|
{
|
||||||
limit: DEFAULT_LIMIT,
|
limit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -39,17 +59,23 @@ export function useGetTrendsQuery() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if (!data.recIdStr) {
|
||||||
|
logger.debug('useGetTrendsQuery response missing recIdStr')
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
select: useCallback(
|
select: useCallback(
|
||||||
(data: AppBskyUnspeccedGetTrends.OutputSchema) => {
|
(data: AppBskyUnspeccedGetTrends.OutputSchema) => {
|
||||||
return {
|
return {
|
||||||
trends: (data.trends ?? []).filter(t => {
|
recId: data.recIdStr,
|
||||||
|
trends: dedupe(
|
||||||
|
(data.trends ?? []).filter(t => {
|
||||||
return !hasMutedWord({
|
return !hasMutedWord({
|
||||||
mutedWords,
|
mutedWords,
|
||||||
text: t.topic + ' ' + t.displayName + ' ' + t.category,
|
text: `${t.topic} ${t.displayName} ${t.category}`,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mutedWords],
|
[mutedWords],
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import {useCallback, useMemo} from 'react'
|
|
||||||
import {type AppBskyUnspeccedDefs, hasMutedWord} from '@atproto/api'
|
|
||||||
import {useQuery} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {STALE} from '#/state/queries'
|
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
|
||||||
import {useAgent} from '#/state/session'
|
|
||||||
|
|
||||||
export type TrendingTopic = AppBskyUnspeccedDefs.TrendingTopic
|
|
||||||
|
|
||||||
type Response = {
|
|
||||||
topics: TrendingTopic[]
|
|
||||||
suggested: TrendingTopic[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_LIMIT = 14
|
|
||||||
|
|
||||||
function dedup(topics: TrendingTopic[]): TrendingTopic[] {
|
|
||||||
const seen = new Set<string>()
|
|
||||||
return topics.filter(t => {
|
|
||||||
if (seen.has(t.link)) return false
|
|
||||||
seen.add(t.link)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const trendingTopicsQueryKey = ['trending-topics']
|
|
||||||
|
|
||||||
export function useTrendingTopics() {
|
|
||||||
const agent = useAgent()
|
|
||||||
const {data: preferences} = usePreferencesQuery()
|
|
||||||
const mutedWords = useMemo(
|
|
||||||
() => preferences?.moderationPrefs?.mutedWords ?? [],
|
|
||||||
[preferences?.moderationPrefs?.mutedWords],
|
|
||||||
)
|
|
||||||
|
|
||||||
return useQuery<Response>({
|
|
||||||
refetchOnWindowFocus: true,
|
|
||||||
staleTime: STALE.MINUTES.THREE,
|
|
||||||
queryKey: trendingTopicsQueryKey,
|
|
||||||
async queryFn() {
|
|
||||||
const {data} = await agent.app.bsky.unspecced.getTrendingTopics({
|
|
||||||
limit: DEFAULT_LIMIT,
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
topics: data.topics ?? [],
|
|
||||||
suggested: data.suggested ?? [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
select: useCallback(
|
|
||||||
(data: Response) => {
|
|
||||||
return {
|
|
||||||
topics: dedup(
|
|
||||||
data.topics.filter(t => {
|
|
||||||
return !hasMutedWord({
|
|
||||||
mutedWords,
|
|
||||||
text: `${t.topic} ${t.displayName ?? ''} ${t.description ?? ''}`,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
suggested: dedup(
|
|
||||||
data.suggested.filter(t => {
|
|
||||||
return !hasMutedWord({
|
|
||||||
mutedWords,
|
|
||||||
text: `${t.topic} ${t.displayName ?? ''} ${t.description ?? ''}`,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[mutedWords],
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
useTrendingSettings,
|
useTrendingSettings,
|
||||||
useTrendingSettingsApi,
|
useTrendingSettingsApi,
|
||||||
} from '#/state/preferences/trending'
|
} from '#/state/preferences/trending'
|
||||||
import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics'
|
import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery'
|
||||||
import {useTrendingConfig} from '#/state/service-config'
|
import {useTrendingConfig} from '#/state/service-config'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
@@ -30,8 +30,14 @@ function Inner() {
|
|||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const trendingPrompt = Prompt.usePromptControl()
|
const trendingPrompt = Prompt.usePromptControl()
|
||||||
const {setTrendingDisabled} = useTrendingSettingsApi()
|
const {setTrendingDisabled} = useTrendingSettingsApi()
|
||||||
const {data: trending, error, isLoading} = useTrendingTopics()
|
const {
|
||||||
const noTopics = !isLoading && !error && !trending?.topics?.length
|
data: trending,
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
} = useGetTrendsQuery({
|
||||||
|
refetchOnWindowFocus: true,
|
||||||
|
})
|
||||||
|
const noTopics = !isLoading && !error && !trending?.trends?.length
|
||||||
|
|
||||||
const onConfirmHide = () => {
|
const onConfirmHide = () => {
|
||||||
ax.metric('trendingTopics:hide', {context: 'sidebar'})
|
ax.metric('trendingTopics:hide', {context: 'sidebar'})
|
||||||
@@ -82,14 +88,17 @@ function Inner() {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
))
|
))
|
||||||
) : !trending?.topics ? null : (
|
) : !trending?.trends ? null : (
|
||||||
<>
|
<>
|
||||||
{trending.topics.slice(0, TRENDING_LIMIT).map((topic, i) => (
|
{trending.trends.slice(0, TRENDING_LIMIT).map((topic, i) => (
|
||||||
<TrendingTopicLink
|
<TrendingTopicLink
|
||||||
key={topic.link}
|
key={topic.link}
|
||||||
topic={topic}
|
topic={topic}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
ax.metric('trendingTopic:click', {context: 'sidebar'})
|
ax.metric('trendingTopic:click', {
|
||||||
|
context: 'sidebar',
|
||||||
|
recId: trending.recId,
|
||||||
|
})
|
||||||
}}>
|
}}>
|
||||||
{({hovered}) => (
|
{({hovered}) => (
|
||||||
<View style={[a.flex_1, a.flex_row, a.gap_xs]}>
|
<View style={[a.flex_1, a.flex_row, a.gap_xs]}>
|
||||||
|
|||||||
Reference in New Issue
Block a user