Update trending topics UI (#11304)
This commit is contained in:
@@ -596,11 +596,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/interstitials/TrendingVideos.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/verification/VerificationCreatePrompt.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
atoms as a,
|
||||
native,
|
||||
useBreakpoints,
|
||||
useGutters,
|
||||
useTheme,
|
||||
type ViewStyleProp,
|
||||
web,
|
||||
@@ -60,10 +61,10 @@ function CardOuter({
|
||||
a.flex_1,
|
||||
a.w_full,
|
||||
a.p_md,
|
||||
a.rounded_lg,
|
||||
a.rounded_xl,
|
||||
a.border,
|
||||
t.atoms.bg,
|
||||
t.atoms.shadow_sm,
|
||||
t.atoms.shadow_md,
|
||||
t.atoms.border_contrast_low,
|
||||
!gtMobile && {
|
||||
width: MOBILE_CARD_WIDTH,
|
||||
@@ -204,6 +205,7 @@ export function ProfileGrid({
|
||||
const {t: l} = useLingui()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const gutters = useGutters([0, 'base'])
|
||||
const followDialogControl = useDialogControl()
|
||||
|
||||
const isLoading = isSuggestionsLoading || !moderationOpts
|
||||
@@ -414,7 +416,7 @@ export function ProfileGrid({
|
||||
moderationOpts={moderationOpts}
|
||||
logContext="FeedInterstitial"
|
||||
withIcon={false}
|
||||
style={[a.rounded_sm]}
|
||||
style={[a.rounded_full]}
|
||||
onFollow={() => {
|
||||
ax.metric('suggestedUser:follow', {
|
||||
logContext,
|
||||
@@ -458,40 +460,43 @@ export function ProfileGrid({
|
||||
pointerEvents={IS_IOS ? 'auto' : 'box-none'}>
|
||||
<View
|
||||
style={[
|
||||
a.px_lg,
|
||||
gutters,
|
||||
a.pt_md,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
]}
|
||||
pointerEvents={IS_IOS ? 'auto' : 'box-none'}>
|
||||
<Text style={[a.text_sm, a.font_semi_bold, t.atoms.text]}>
|
||||
<Trans>Suggested for you</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
label={l`See more suggested profiles`}
|
||||
onPress={() => {
|
||||
followDialogControl.open()
|
||||
ax.metric('suggestedUser:seeMore', {
|
||||
logContext,
|
||||
recId,
|
||||
})
|
||||
}}>
|
||||
{({hovered}) => (
|
||||
<Text
|
||||
style={[
|
||||
a.text_sm,
|
||||
t.atoms.text_link,
|
||||
hovered &&
|
||||
web({
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor: t.atoms.text_link.color,
|
||||
}),
|
||||
]}>
|
||||
<Trans>See more</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Button>
|
||||
<View style={[a.w_full, a.pl_xs, a.flex_row, a.align_center]}>
|
||||
<Text style={[a.flex_1, a.text_md, a.font_semi_bold]}>
|
||||
<Trans>Suggested for you</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
label={l`See more suggested profiles`}
|
||||
onPress={() => {
|
||||
followDialogControl.open()
|
||||
ax.metric('suggestedUser:seeMore', {
|
||||
logContext,
|
||||
recId,
|
||||
})
|
||||
}}>
|
||||
{({hovered, pressed}) => (
|
||||
<Text
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_medium,
|
||||
{
|
||||
color:
|
||||
hovered || pressed
|
||||
? t.palette.contrast_800
|
||||
: t.palette.contrast_500,
|
||||
},
|
||||
]}>
|
||||
<Trans>See more</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<FollowDialogWithoutGuide control={followDialogControl} />
|
||||
<LayoutAnimationConfig skipExiting skipEntering>
|
||||
@@ -540,7 +545,7 @@ function SeeMoreSuggestedProfilesCard({onPress}: {onPress: () => void}) {
|
||||
a.justify_center,
|
||||
a.gap_sm,
|
||||
a.p_md,
|
||||
a.rounded_lg,
|
||||
a.rounded_xl,
|
||||
{width: FINAL_CARD_WIDTH},
|
||||
]}>
|
||||
<ButtonIcon icon={ArrowRight} size="lg" />
|
||||
|
||||
@@ -601,7 +601,7 @@ export function FollowButtonPlaceholder({style}: ViewStyleProp) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.rounded_sm,
|
||||
a.rounded_full,
|
||||
t.atoms.bg_contrast_50,
|
||||
a.w_full,
|
||||
{
|
||||
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
AppBskyFeedPost,
|
||||
type ModerationDecision,
|
||||
} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {formatCount} from '#/view/com/util/numeric/format'
|
||||
@@ -52,7 +51,7 @@ export function VideoPostCard({
|
||||
onInteract?: () => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_, i18n} = useLingui()
|
||||
const {t: l, i18n} = useLingui()
|
||||
const embed = post.embed
|
||||
const {
|
||||
state: pressed,
|
||||
@@ -118,8 +117,8 @@ export function VideoPostCard({
|
||||
|
||||
return (
|
||||
<Link
|
||||
accessibilityHint={_(msg`Views video in immersive mode`)}
|
||||
label={_(msg`Video from ${author.handle}: ${text}`)}
|
||||
accessibilityHint={l`Views video in immersive mode`}
|
||||
label={l`Video from ${author.handle}: ${text}`}
|
||||
to={{
|
||||
screen: 'VideoFeed',
|
||||
params: {
|
||||
@@ -174,9 +173,7 @@ export function VideoPostCard({
|
||||
/>
|
||||
<View style={[a.align_center, a.gap_xs]}>
|
||||
<Eye size="lg" fill="white" />
|
||||
<Text style={[a.text_sm, {color: 'white'}]}>
|
||||
{_(msg`Hidden`)}
|
||||
</Text>
|
||||
<Text style={[a.text_sm, {color: 'white'}]}>{l`Hidden`}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -367,7 +364,7 @@ export function CompactVideoPostCard({
|
||||
onInteract?: () => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_, i18n} = useLingui()
|
||||
const {t: l, i18n} = useLingui()
|
||||
const embed = post.embed
|
||||
const {
|
||||
state: pressed,
|
||||
@@ -398,7 +395,7 @@ export function CompactVideoPostCard({
|
||||
|
||||
return (
|
||||
<Link
|
||||
label={_(msg`View video`)}
|
||||
label={l`View video`}
|
||||
to={{
|
||||
screen: 'VideoFeed',
|
||||
params: {
|
||||
@@ -413,7 +410,8 @@ export function CompactVideoPostCard({
|
||||
onPressOut={onPressOut}
|
||||
style={[
|
||||
a.flex_col,
|
||||
t.atoms.shadow_sm,
|
||||
a.rounded_xl,
|
||||
t.atoms.shadow_md,
|
||||
{
|
||||
alignItems: undefined,
|
||||
justifyContent: undefined,
|
||||
@@ -424,7 +422,7 @@ export function CompactVideoPostCard({
|
||||
<View
|
||||
style={[
|
||||
a.justify_center,
|
||||
a.rounded_lg,
|
||||
a.rounded_xl,
|
||||
a.overflow_hidden,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
@@ -458,9 +456,7 @@ export function CompactVideoPostCard({
|
||||
/>
|
||||
<View style={[a.align_center, a.gap_xs]}>
|
||||
<Eye size="lg" fill="white" />
|
||||
<Text style={[a.text_sm, {color: 'white'}]}>
|
||||
{_(msg`Hidden`)}
|
||||
</Text>
|
||||
<Text style={[a.text_sm, {color: 'white'}]}>{l`Hidden`}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -469,7 +465,7 @@ export function CompactVideoPostCard({
|
||||
<View
|
||||
style={[
|
||||
a.justify_center,
|
||||
a.rounded_lg,
|
||||
a.rounded_xl,
|
||||
a.overflow_hidden,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
@@ -485,10 +481,17 @@ export function CompactVideoPostCard({
|
||||
/>
|
||||
<MediaInsetBorder />
|
||||
|
||||
<View style={[a.absolute, a.inset_0, t.atoms.shadow_sm]}>
|
||||
<View style={[a.absolute, a.inset_0, t.atoms.shadow_md]}>
|
||||
<View style={[a.absolute, a.inset_0, a.p_sm, {bottom: 'auto'}]}>
|
||||
<View
|
||||
style={[a.relative, a.rounded_full, {width: 24, height: 24}]}>
|
||||
style={[
|
||||
a.relative,
|
||||
a.rounded_full,
|
||||
{
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
]}>
|
||||
<UserAvatar
|
||||
type="user"
|
||||
size={24}
|
||||
@@ -547,10 +550,10 @@ export function CompactVideoPostCardPlaceholder() {
|
||||
const black = getBlackColor(t)
|
||||
|
||||
return (
|
||||
<View style={[a.flex_1, t.atoms.shadow_sm]}>
|
||||
<View style={[a.flex_1, t.atoms.shadow_md]}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_lg,
|
||||
a.rounded_xl,
|
||||
a.overflow_hidden,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
|
||||
@@ -4,7 +4,10 @@ import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useTrendingSettings} from '#/state/preferences/trending'
|
||||
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'
|
||||
@@ -16,10 +19,12 @@ import {
|
||||
useTheme,
|
||||
type ViewStyleProp,
|
||||
} from '#/alf'
|
||||
import {alpha} from '#/alf/utils'
|
||||
import {AvatarStack} from '#/components/AvatarStack'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid'
|
||||
import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/icons/Trending'
|
||||
import {Link} from '#/components/Link'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
@@ -39,6 +44,8 @@ function Inner() {
|
||||
const {t: l} = useLingui()
|
||||
const gutters = useGutters([0, 'base'])
|
||||
const ax = useAnalytics()
|
||||
const trendingPrompt = Prompt.usePromptControl()
|
||||
const {setTrendingDisabled} = useTrendingSettingsApi()
|
||||
const {
|
||||
data: trending,
|
||||
error,
|
||||
@@ -47,90 +54,109 @@ function Inner() {
|
||||
} = useGetTrendsQuery({limit: TOPIC_COUNT})
|
||||
const noTopics = !isLoading && !error && !trending?.trends?.length
|
||||
|
||||
const shadowColor = alpha(t.palette.primary_100, 0.5)
|
||||
|
||||
if (error || noTopics) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
gutters,
|
||||
a.pt_lg,
|
||||
a.pb_xl,
|
||||
a.gap_sm,
|
||||
a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.relative,
|
||||
a.z_20,
|
||||
a.px_xs,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_sm,
|
||||
gutters,
|
||||
a.pt_xs,
|
||||
a.pb_lg,
|
||||
a.gap_xs,
|
||||
a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<View style={[a.flex_row, a.align_center, a.justify_between, a.gap_xs]}>
|
||||
<TrendingIcon width={18} />
|
||||
<Text style={[a.text_md, a.font_medium]} numberOfLines={1}>
|
||||
<Trans>Trending</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<Link label={l`See more trending topics`} to="/search">
|
||||
{({hovered, pressed}) => (
|
||||
<Text
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_medium,
|
||||
{
|
||||
color:
|
||||
hovered || pressed
|
||||
? t.palette.contrast_800
|
||||
: t.palette.contrast_500,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}>
|
||||
<Trans>See more</Trans>
|
||||
<View
|
||||
style={[
|
||||
a.relative,
|
||||
a.z_20,
|
||||
a.pl_xs,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_sm,
|
||||
]}>
|
||||
<View
|
||||
style={[a.flex_row, a.align_center, a.justify_between, a.gap_xs]}>
|
||||
<TrendingIcon width={18} fill={t.atoms.text.color} />
|
||||
<Text style={[a.text_md, a.font_medium]} numberOfLines={1}>
|
||||
<Trans>Trending</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Link>
|
||||
</View>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Link label={l`See more trending topics`} to="/search">
|
||||
{({hovered, pressed}) => (
|
||||
<Text
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_medium,
|
||||
{
|
||||
color:
|
||||
hovered || pressed
|
||||
? t.palette.contrast_800
|
||||
: t.palette.contrast_500,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}>
|
||||
<Trans>See more</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="medium"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
label={l`Trending options`}
|
||||
onPress={() => trendingPrompt.open()}
|
||||
style={[a.bg_transparent]}>
|
||||
<ButtonIcon icon={EllipsisIcon} size="md" />
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[a.relative, a.z_10, a.rounded_xl, t.atoms.shadow_md]}>
|
||||
<View
|
||||
style={[
|
||||
a.overflow_hidden,
|
||||
a.border,
|
||||
a.rounded_xl,
|
||||
t.atoms.bg,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
{isLoading || isRefetching
|
||||
? Array.from({length: TOPIC_COUNT}).map((_, i) => (
|
||||
<TrendingTopicRowSkeleton key={i} rank={i + 1} />
|
||||
))
|
||||
: trending?.trends?.map((trend, index) => (
|
||||
<TrendRow
|
||||
key={trend.link}
|
||||
trend={trend}
|
||||
rank={index + 1}
|
||||
onPress={() => {
|
||||
ax.metric('trendingTopic:click', {
|
||||
context: 'interstitial',
|
||||
})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
a.relative,
|
||||
a.z_10,
|
||||
a.border,
|
||||
a.rounded_xl,
|
||||
t.atoms.bg,
|
||||
{
|
||||
borderColor: t.palette.primary_100,
|
||||
boxShadow: `0 0 16px 0 ${shadowColor}`,
|
||||
elevation: 8,
|
||||
shadowColor: shadowColor,
|
||||
shadowOffset: {width: 0, height: 0},
|
||||
shadowOpacity: 1,
|
||||
shadowRadius: 16,
|
||||
},
|
||||
]}>
|
||||
{isLoading || isRefetching
|
||||
? Array.from({length: TOPIC_COUNT}).map((_, i) => (
|
||||
<TrendingTopicRowSkeleton key={i} rank={i + 1} />
|
||||
))
|
||||
: trending?.trends?.map((trend, index) => (
|
||||
<TrendRow
|
||||
key={trend.link}
|
||||
trend={trend}
|
||||
rank={index + 1}
|
||||
onPress={() => {
|
||||
ax.metric('trendingTopic:click', {context: 'interstitial'})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<Prompt.Basic
|
||||
control={trendingPrompt}
|
||||
title={l`Hide trending topics?`}
|
||||
description={l`You can update this later from your settings.`}
|
||||
confirmButtonCta={l`Hide`}
|
||||
onConfirm={() => {
|
||||
ax.metric('trendingTopics:hide', {context: 'interstitial'})
|
||||
setTrendingDisabled(true)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -156,12 +182,7 @@ function TrendRow({
|
||||
label={l`Browse topic ${trend.displayName}`}
|
||||
to={trend.link}
|
||||
onPress={onPress}
|
||||
style={[
|
||||
rank < TOPIC_COUNT && a.border_b,
|
||||
{
|
||||
borderColor: t.palette.primary_100,
|
||||
},
|
||||
]}
|
||||
style={[rank < TOPIC_COUNT && a.border_b, t.atoms.border_contrast_low]}
|
||||
PressableComponent={Pressable}>
|
||||
{({hovered, pressed}) => (
|
||||
<>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {useCallback, useEffect, useMemo} from 'react'
|
||||
import {ScrollView, View} from 'react-native'
|
||||
import {AppBskyEmbedVideo, AtUri} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {VIDEO_FEED_URI} from '#/lib/constants'
|
||||
@@ -14,7 +12,7 @@ import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
|
||||
import {atoms as a, useGutters, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid'
|
||||
import {Link} from '#/components/Link'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -35,7 +33,7 @@ const FEED_PARAMS: {
|
||||
|
||||
export function TrendingVideos() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const gutters = useGutters([0, 'base'])
|
||||
const {data, isLoading, error} = usePostFeedQuery(FEED_DESC, FEED_PARAMS)
|
||||
@@ -48,7 +46,7 @@ export function TrendingVideos() {
|
||||
.getQueryCache()
|
||||
.find({queryKey: RQKEY(FEED_DESC, FEED_PARAMS)})
|
||||
if (query && query.getObserversCount() <= 1) {
|
||||
query.fetch()
|
||||
void query.fetch()
|
||||
}
|
||||
}
|
||||
}, [queryClient])
|
||||
@@ -83,20 +81,22 @@ export function TrendingVideos() {
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
]}>
|
||||
<Text style={[a.text_sm, a.font_semi_bold, a.leading_snug]}>
|
||||
<Trans>Trending Videos</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
label={_(msg`Dismiss this section`)}
|
||||
size="tiny"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="square"
|
||||
onPress={() => trendingPrompt.open()}>
|
||||
<ButtonIcon icon={X} size="sm" />
|
||||
</Button>
|
||||
<View style={[a.pl_xs, a.flex_row, a.align_center]}>
|
||||
<Text style={[a.flex_1, a.text_md, a.font_semi_bold]}>
|
||||
<Trans>Trending videos</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
label={l`Dismiss this section`}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
style={[a.bg_transparent]}
|
||||
onPress={() => trendingPrompt.open()}>
|
||||
<ButtonIcon icon={EllipsisIcon} size="md" />
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<BlockDrawerGesture>
|
||||
<ScrollView
|
||||
horizontal
|
||||
@@ -131,12 +131,11 @@ export function TrendingVideos() {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</BlockDrawerGesture>
|
||||
|
||||
<Prompt.Basic
|
||||
control={trendingPrompt}
|
||||
title={_(msg`Hide trending videos?`)}
|
||||
description={_(msg`You can update this later from your settings.`)}
|
||||
confirmButtonCta={_(msg`Hide`)}
|
||||
title={l`Hide trending videos?`}
|
||||
description={l`You can update this later from your settings.`}
|
||||
confirmButtonCta={l`Hide`}
|
||||
onConfirm={onConfirmHide}
|
||||
/>
|
||||
</View>
|
||||
@@ -186,7 +185,7 @@ function VideoCards({
|
||||
|
||||
function ViewMoreCard() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
|
||||
const href = useMemo(() => {
|
||||
const urip = new AtUri(VIDEO_FEED_URI)
|
||||
@@ -197,16 +196,16 @@ function ViewMoreCard() {
|
||||
<View style={[{width: CARD_WIDTH * 2}]}>
|
||||
<Link
|
||||
to={href}
|
||||
label={_(msg`View more`)}
|
||||
label={l`View more`}
|
||||
style={[
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
a.flex_1,
|
||||
a.rounded_lg,
|
||||
a.rounded_xl,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg,
|
||||
t.atoms.shadow_sm,
|
||||
t.atoms.shadow_md,
|
||||
]}>
|
||||
{({pressed}) => (
|
||||
<View
|
||||
@@ -225,7 +224,7 @@ function ViewMoreCard() {
|
||||
color="primary"
|
||||
size="small"
|
||||
shape="round"
|
||||
label={_(msg`View more trending videos`)}>
|
||||
label={l`View more trending videos`}>
|
||||
<ButtonIcon icon={ChevronRight} />
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
import {sizes as iconSizes} from '#/components/icons/common'
|
||||
import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass'
|
||||
import {Link} from '#/components/Link'
|
||||
import {Text, type TextProps} from '#/components/Typography'
|
||||
@@ -151,6 +152,32 @@ export function SearchButton({
|
||||
)
|
||||
}
|
||||
|
||||
export function EllipsisButton({
|
||||
label,
|
||||
onPress,
|
||||
}: {
|
||||
label: string
|
||||
onPress?: () => void
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
label={label}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
PressableComponent={native(PressableScale)}
|
||||
onPress={onPress}
|
||||
style={[
|
||||
{
|
||||
right: -4,
|
||||
},
|
||||
]}>
|
||||
<ButtonIcon icon={EllipsisIcon} size="md" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function PinButton({feed}: {feed: AppBskyFeedDefs.GeneratorView}) {
|
||||
return (
|
||||
<View style={[a.z_20, {marginRight: -6}]}>
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useTrendingSettings} from '#/state/preferences/trending'
|
||||
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'
|
||||
@@ -18,6 +21,7 @@ import {atoms as a, useGutters, useTheme, type ViewStyleProp} from '#/alf'
|
||||
import {AvatarStack} from '#/components/AvatarStack'
|
||||
import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/icons/Trending'
|
||||
import {Link} from '#/components/Link'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -36,6 +40,9 @@ export function ExploreTrendingTopics() {
|
||||
|
||||
function Inner() {
|
||||
const ax = useAnalytics()
|
||||
const {t: l} = useLingui()
|
||||
const trendingPrompt = Prompt.usePromptControl()
|
||||
const {setTrendingDisabled} = useTrendingSettingsApi()
|
||||
const {data: trending, error, isLoading, isRefetching} = useGetTrendsQuery()
|
||||
const noTopics = !isLoading && !error && !trending?.trends?.length
|
||||
const showLoading = isLoading || isRefetching
|
||||
@@ -43,31 +50,48 @@ function Inner() {
|
||||
if (!showLoading && (error || !trending?.trends || noTopics)) return null
|
||||
|
||||
return (
|
||||
<View style={[a.pb_md]}>
|
||||
<ModuleHeader.Container bottomBorder>
|
||||
<ModuleHeader.Icon icon={TrendingIcon} size="md" />
|
||||
<ModuleHeader.TitleText>
|
||||
<Trans>Trending</Trans>
|
||||
</ModuleHeader.TitleText>
|
||||
</ModuleHeader.Container>
|
||||
{showLoading
|
||||
? Array.from({length: TOPIC_COUNT}).map((__, i) => (
|
||||
<TrendingTopicRowSkeleton key={i} />
|
||||
))
|
||||
: trending?.trends.map((trend, index) => (
|
||||
<TrendRow
|
||||
key={trend.link}
|
||||
trend={trend}
|
||||
rank={index + 1}
|
||||
onPress={() => {
|
||||
ax.metric('trendingTopic:click', {
|
||||
context: 'explore',
|
||||
recId: trending.recId,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<>
|
||||
<View style={[a.pb_md]}>
|
||||
<ModuleHeader.Container bottomBorder>
|
||||
<ModuleHeader.Icon icon={TrendingIcon} size="md" />
|
||||
<ModuleHeader.TitleText>
|
||||
<Trans>Trending</Trans>
|
||||
</ModuleHeader.TitleText>
|
||||
<ModuleHeader.EllipsisButton
|
||||
label={l`Trending options`}
|
||||
onPress={() => trendingPrompt.open()}
|
||||
/>
|
||||
</ModuleHeader.Container>
|
||||
{showLoading
|
||||
? Array.from({length: TOPIC_COUNT}).map((__, i) => (
|
||||
<TrendingTopicRowSkeleton key={i} />
|
||||
))
|
||||
: trending?.trends.map((trend, index) => (
|
||||
<TrendRow
|
||||
key={trend.link}
|
||||
trend={trend}
|
||||
rank={index + 1}
|
||||
onPress={() => {
|
||||
ax.metric('trendingTopic:click', {
|
||||
context: 'explore',
|
||||
recId: trending.recId,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<Prompt.Basic
|
||||
control={trendingPrompt}
|
||||
title={l`Hide trending topics?`}
|
||||
description={l`You can update this later from your settings.`}
|
||||
confirmButtonCta={l`Hide`}
|
||||
onConfirm={() => {
|
||||
ax.metric('trendingTopics:hide', {context: 'explore:trending'})
|
||||
setTrendingDisabled(true)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -112,7 +136,7 @@ export function TrendRow({
|
||||
<View style={[gutters, a.w_full, a.flex_row, a.py_md, a.gap_sm]}>
|
||||
<Text
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.text_md,
|
||||
a.font_medium,
|
||||
|
||||
t.atoms.text_contrast_low,
|
||||
@@ -126,7 +150,7 @@ export function TrendRow({
|
||||
</Text>
|
||||
<View style={[a.flex_1, a.gap_2xs]}>
|
||||
<Text
|
||||
style={[a.text_sm, a.font_semi_bold, a.leading_snug]}
|
||||
style={[a.text_md, a.font_semi_bold, a.leading_snug]}
|
||||
numberOfLines={1}>
|
||||
{trend.displayName}
|
||||
</Text>
|
||||
|
||||
@@ -211,6 +211,10 @@ export type PostFeedRef = {
|
||||
// const REFRESH_AFTER = STALE.HOURS.ONE
|
||||
const CHECK_LATEST_AFTER = STALE.SECONDS.THIRTY
|
||||
|
||||
const TRENDING_TOPICS_INDEX = 5
|
||||
const TRENDING_VIDEO_INDEX = 30
|
||||
const SUGGESTED_FOR_YOU_INDEX = 15
|
||||
|
||||
let PostFeed = ({
|
||||
feed,
|
||||
description,
|
||||
@@ -570,19 +574,19 @@ let PostFeed = ({
|
||||
key: 'composerPrompt-' + sliceIndex,
|
||||
})
|
||||
}
|
||||
} else if (sliceIndex === 1) {
|
||||
} else if (sliceIndex === TRENDING_TOPICS_INDEX) {
|
||||
arr.push({
|
||||
type: 'interstitialFeedTrendingTopics',
|
||||
key: 'interstitialFeedTrendingTopics-' + sliceIndex,
|
||||
})
|
||||
} else if (sliceIndex === 15) {
|
||||
} else if (sliceIndex === TRENDING_VIDEO_INDEX) {
|
||||
if (areVideoFeedsEnabled && !trendingVideoDisabled) {
|
||||
arr.push({
|
||||
type: 'interstitialTrendingVideos',
|
||||
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
|
||||
})
|
||||
}
|
||||
} else if (sliceIndex === 30) {
|
||||
} else if (sliceIndex === SUGGESTED_FOR_YOU_INDEX) {
|
||||
arr.push({
|
||||
type: 'interstitialFollows',
|
||||
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery'
|
||||
import {useTrendingConfig} from '#/state/service-config'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {DotGrid3x1_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
|
||||
import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid'
|
||||
import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/icons/Trending'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {TrendingTopicLink} from '#/components/TrendingTopics'
|
||||
@@ -50,7 +50,7 @@ function Inner() {
|
||||
style={[a.p_lg, a.rounded_md, a.border, t.atoms.border_contrast_low]}>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs, a.pb_md]}>
|
||||
<TrendingIcon width={16} height={16} fill={t.atoms.text.color} />
|
||||
<Text style={[a.flex_1, a.text_md, a.font_semi_bold, t.atoms.text]}>
|
||||
<Text style={[a.flex_1, a.text_md, a.font_semi_bold]}>
|
||||
<Trans>Trending</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
@@ -60,8 +60,8 @@ function Inner() {
|
||||
shape="round"
|
||||
label={l`Trending options`}
|
||||
onPress={() => trendingPrompt.open()}
|
||||
style={[a.bg_transparent, {marginTop: -6, marginRight: -6}]}>
|
||||
<ButtonIcon icon={Ellipsis} size="xs" />
|
||||
style={[a.bg_transparent]}>
|
||||
<ButtonIcon icon={EllipsisIcon} size="xs" />
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user