diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/posts/PostFeed.tsx | 25 | ||||
-rw-r--r-- | src/view/screens/Search/Explore.tsx | 48 | ||||
-rw-r--r-- | src/view/shell/desktop/Feeds.tsx | 32 | ||||
-rw-r--r-- | src/view/shell/desktop/RightNav.tsx | 44 | ||||
-rw-r--r-- | src/view/shell/desktop/SidebarTrendingTopics.tsx | 104 |
5 files changed, 228 insertions, 25 deletions
diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index 10eb47d0a..7860d568d 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -23,6 +23,7 @@ import {logger} from '#/logger' import {isIOS, isWeb} from '#/platform/detection' import {listenPostCreated} from '#/state/events' import {useFeedFeedbackContext} from '#/state/feed-feedback' +import {useTrendingSettings} from '#/state/preferences/trending' import {STALE} from '#/state/queries' import { FeedDescriptor, @@ -34,7 +35,9 @@ import { } from '#/state/queries/post-feed' import {useSession} from '#/state/session' import {useProgressGuide} from '#/state/shell/progress-guide' +import {useBreakpoints} from '#/alf' import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials' +import {TrendingInterstitial} from '#/components/interstitials/Trending' import {List, ListRef} from '../util/List' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' @@ -90,6 +93,10 @@ type FeedRow = type: 'interstitialProgressGuide' key: string } + | { + type: 'interstitialTrending' + key: string + } export function getFeedPostSlice(feedRow: FeedRow): FeedPostSlice | null { if (feedRow.type === 'sliceItem') { @@ -156,6 +163,7 @@ let PostFeed = ({ const checkForNewRef = React.useRef<(() => void) | null>(null) const lastFetchRef = React.useRef<number>(Date.now()) const [feedType, feedUri, feedTab] = feed.split('|') + const {gtTablet} = useBreakpoints() const opts = React.useMemo( () => ({enabled, ignoreFilterFor}), @@ -259,6 +267,8 @@ let PostFeed = ({ const showProgressIntersitial = (followProgressGuide || followAndLikeProgressGuide) && !isDesktop + const {trendingDisabled} = useTrendingSettings() + const feedItems: FeedRow[] = React.useMemo(() => { let feedKind: 'following' | 'discover' | 'profile' | undefined if (feedType === 'following') { @@ -304,7 +314,16 @@ let PostFeed = ({ type: 'interstitialProgressGuide', key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, }) - } else if (sliceIndex === 20) { + } else if ( + sliceIndex === 15 && + !gtTablet && + !trendingDisabled + ) { + arr.push({ + type: 'interstitialTrending', + key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, + }) + } else if (sliceIndex === 30) { arr.push({ type: 'interstitialFollows', key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, @@ -390,6 +409,8 @@ let PostFeed = ({ feedTab, hasSession, showProgressIntersitial, + trendingDisabled, + gtTablet, ]) // events @@ -476,6 +497,8 @@ let PostFeed = ({ return <SuggestedFollows feed={feed} /> } else if (row.type === 'interstitialProgressGuide') { return <ProgressGuide /> + } else if (row.type === 'interstitialTrending') { + return <TrendingInterstitial /> } else if (row.type === 'sliceItem') { const slice = row.slice if (slice.isFallbackMarker) { diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index bd2ebe5d5..378ea59a4 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -24,6 +24,8 @@ import { ProfileCardFeedLoadingPlaceholder, } from '#/view/com/util/LoadingPlaceholder' import {UserAvatar} from '#/view/com/util/UserAvatar' +import {ExploreRecommendations} from '#/screens/Search/components/ExploreRecommendations' +import {ExploreTrendingTopics} from '#/screens/Search/components/ExploreTrendingTopics' import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {Button} from '#/components/Button' import * as FeedCard from '#/components/FeedCard' @@ -240,6 +242,14 @@ type ExploreScreenItems = icon: React.ComponentType<SVGIconProps> } | { + type: 'trendingTopics' + key: string + } + | { + type: 'recommendations' + key: string + } + | { type: 'profile' key: string profile: AppBskyActorDefs.ProfileView @@ -325,17 +335,27 @@ export function Explore() { ]) const items = React.useMemo<ExploreScreenItems[]>(() => { - const i: ExploreScreenItems[] = [ - { - type: 'header', - key: 'suggested-follows-header', - title: _(msg`Suggested accounts`), - description: _( - msg`Follow more accounts to get connected to your interests and build your network.`, - ), - icon: Person, - }, - ] + const i: ExploreScreenItems[] = [] + + i.push({ + type: 'trendingTopics', + key: `trending-topics`, + }) + + i.push({ + type: 'recommendations', + key: `recommendations`, + }) + + i.push({ + type: 'header', + key: 'suggested-follows-header', + title: _(msg`Suggested accounts`), + description: _( + msg`Follow more accounts to get connected to your interests and build your network.`, + ), + icon: Person, + }) if (profiles) { // Currently the responses contain duplicate items. @@ -490,6 +510,12 @@ export function Explore() { /> ) } + case 'trendingTopics': { + return <ExploreTrendingTopics /> + } + case 'recommendations': { + return <ExploreRecommendations /> + } case 'profile': { return ( <View style={[a.border_b, t.atoms.border_contrast_low]}> diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index 83b5420ce..1d515df55 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -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, /* diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 895d16021..363294aa5 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -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 && ( <> diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx new file mode 100644 index 000000000..e22fad54d --- /dev/null +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -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 /> + </> + ) +} |