Trending (Beta) (#7144)

* Add WIP UIs for trending topics and suggested starterpacks

* Disable SPs for now

* Improve explore treatment a bit, add some polish to cards

* Add tiny option in RightNav

* Add persisted option to hide trending from sidebar

* Add to settings, abstract state, not updating in tab

* Fix up hide/show toggle state, WITH broadcast hacK

* Clean up persisted code, add new setting

* Add new interstitial to Discover

* Exploration

* First hack at mute words

* Wire up interstitial and Explore page

* Align components

* Some skeleton UI

* Handle service config, enablement, load states, update lex contract

* Centralize mute word handling

* Stale time to 30m

* Cache enabled value for reloads, use real data for service config

* Remove broadcast hack

* Remove titleChild

* Gate settings too

* Update package, rm langs

* Add feature gate

* Only english during beta period

* Hook up real data

* Tweak config

* Straight passthrough links

* Hook up prod agent

* Fix no-show logic

* Up config query to 5 min

* Remove old file

* Remove comment

* Remove stray flex_1

* Make trending setting global

* Quick placeholder state

* Limit # in sidebar, tweak spacing

* Tweak gaps

* Handle hide/show of sidebar

* Simplify messages

* Remove interstitial

* Revert "Remove interstitial"

This reverts commit 1358ad47fd.

* Only show interstitial on mobile

* Fix gap

* Add explore page recommendations

* [topics] add topic screen (#7149)

* add topic screen

* decode

* fix search query

* decode

* add server route

* Fix potential bad destructure (undefined)

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
Eric Bailey
2024-12-17 21:45:39 -06:00
committed by GitHub
parent a07949ec8e
commit a2019aceec
29 changed files with 1268 additions and 80 deletions
+29 -3
View File
@@ -14,7 +14,7 @@ import {createStaticClick, InlineLinkText} from '#/components/Link'
export function DesktopFeeds() {
const t = useTheme()
const {_} = useLingui()
const {data: pinnedFeedInfos} = usePinnedFeedsInfos()
const {data: pinnedFeedInfos, error, isLoading} = usePinnedFeedsInfos()
const selectedFeed = useSelectedFeed()
const setSelectedFeed = useSetSelectedFeed()
const navigation = useNavigation<NavigationProp>()
@@ -25,14 +25,40 @@ export function DesktopFeeds() {
return getCurrentRoute(state)
})
if (!pinnedFeedInfos) {
if (isLoading) {
return (
<View
style={[
{
gap: 12,
},
]}>
{Array(5)
.fill(0)
.map((_, i) => (
<View
key={i}
style={[
a.rounded_sm,
t.atoms.bg_contrast_25,
{
height: 16,
width: i % 2 === 0 ? '60%' : '80%',
},
]}
/>
))}
</View>
)
}
if (error || !pinnedFeedInfos) {
return null
}
return (
<View
style={[
a.flex_1,
web({
gap: 10,
/*
+34 -10
View File
@@ -1,6 +1,8 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/core'
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
@@ -8,17 +10,41 @@ import {useKawaiiMode} from '#/state/preferences/kawaii'
import {useSession} from '#/state/session'
import {DesktopFeeds} from '#/view/shell/desktop/Feeds'
import {DesktopSearch} from '#/view/shell/desktop/Search'
import {SidebarTrendingTopics} from '#/view/shell/desktop/SidebarTrendingTopics'
import {atoms as a, useGutters, useTheme, web} from '#/alf'
import {Divider} from '#/components/Divider'
import {InlineLinkText} from '#/components/Link'
import {ProgressGuideList} from '#/components/ProgressGuide/List'
import {Text} from '#/components/Typography'
function useWebQueryParams() {
const navigation = useNavigation()
const [params, setParams] = React.useState<Record<string, string>>({})
React.useEffect(() => {
return navigation.addListener('state', e => {
try {
const {state} = e.data
const lastRoute = state.routes[state.routes.length - 1]
const {params} = lastRoute
setParams(params)
} catch (e) {}
})
}, [navigation, setParams])
return params
}
export function DesktopRightNav({routeName}: {routeName: string}) {
const t = useTheme()
const {_} = useLingui()
const {hasSession, currentAccount} = useSession()
const kawaii = useKawaiiMode()
const gutters = useGutters(['base', 0, 'base', 'wide'])
const isSearchScreen = routeName === 'Search'
const webqueryParams = useWebQueryParams()
const searchQuery = webqueryParams?.q
const showTrending = !isSearchScreen || (isSearchScreen && !!searchQuery)
const {isTablet} = useWebMediaQueries()
if (isTablet) {
@@ -29,6 +55,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
<View
style={[
gutters,
a.gap_lg,
web({
position: 'fixed',
left: '50%',
@@ -43,21 +70,18 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
overflowY: 'auto',
}),
]}>
{routeName !== 'Search' && (
<View style={[a.pb_lg]}>
<DesktopSearch />
</View>
)}
{!isSearchScreen && <DesktopSearch />}
{hasSession && (
<>
<ProgressGuideList style={[a.pb_xl]} />
<View
style={[a.pb_lg, a.mb_lg, a.border_b, t.atoms.border_contrast_low]}>
<DesktopFeeds />
</View>
<ProgressGuideList />
<DesktopFeeds />
<Divider />
</>
)}
{showTrending && <SidebarTrendingTopics />}
<Text style={[a.leading_snug, t.atoms.text_contrast_low]}>
{hasSession && (
<>
@@ -0,0 +1,104 @@
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {
useTrendingSettings,
useTrendingSettingsApi,
} from '#/state/preferences/trending'
import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics'
import {useTrendingConfig} from '#/state/trending-config'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending2'
import * as Prompt from '#/components/Prompt'
import {
TrendingTopic,
TrendingTopicLink,
TrendingTopicSkeleton,
} from '#/components/TrendingTopics'
import {Text} from '#/components/Typography'
const TRENDING_LIMIT = 6
export function SidebarTrendingTopics() {
const {enabled} = useTrendingConfig()
const {trendingDisabled} = useTrendingSettings()
return !enabled ? null : trendingDisabled ? null : <Inner />
}
function Inner() {
const t = useTheme()
const {_} = useLingui()
const trendingPrompt = Prompt.usePromptControl()
const {setTrendingDisabled} = useTrendingSettingsApi()
const {data: trending, error, isLoading} = useTrendingTopics()
const noTopics = !isLoading && !error && !trending?.topics?.length
return error || noTopics ? null : (
<>
<View style={[a.gap_sm, {paddingBottom: 2}]}>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Graph size="sm" />
<Text
style={[
a.flex_1,
a.text_sm,
a.font_bold,
t.atoms.text_contrast_medium,
]}>
<Trans>Trending</Trans>
</Text>
<Button
label={_(msg`Hide trending topics`)}
size="tiny"
variant="ghost"
color="secondary"
shape="round"
onPress={() => trendingPrompt.open()}>
<ButtonIcon icon={X} />
</Button>
</View>
<View style={[a.flex_row, a.flex_wrap, {gap: '6px 4px'}]}>
{isLoading ? (
Array(TRENDING_LIMIT)
.fill(0)
.map((_n, i) => (
<TrendingTopicSkeleton key={i} size="small" index={i} />
))
) : !trending?.topics ? null : (
<>
{trending.topics.slice(0, TRENDING_LIMIT).map(topic => (
<TrendingTopicLink key={topic.link} topic={topic}>
{({hovered}) => (
<TrendingTopic
size="small"
topic={topic}
style={[
hovered && [
t.atoms.border_contrast_high,
t.atoms.bg_contrast_25,
],
]}
/>
)}
</TrendingTopicLink>
))}
</>
)}
</View>
</View>
<Prompt.Basic
control={trendingPrompt}
title={_(msg`Hide trending topics?`)}
description={_(msg`You can update this later from your settings.`)}
confirmButtonCta={_(msg`Hide`)}
onConfirm={() => setTrendingDisabled(true)}
/>
<Divider />
</>
)
}