Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d40ed2041b | |||
| 329847df1e | |||
| 0b8042d5a6 | |||
| c8904777ea | |||
| 3ed2aef7a6 | |||
| f4c41158a3 | |||
| d1c3ae59ec | |||
| 0a2a058681 | |||
| ed4295f8d0 | |||
| 8da7e0ad8c | |||
| 57ee0f61c2 | |||
| b25aa7eda8 | |||
| 67c79dd8ba | |||
| 2cc39b1458 |
@@ -0,0 +1,34 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme, type ViewStyleProp} from '#/alf'
|
||||
|
||||
export function SubtleHover({style, hover}: ViewStyleProp & {hover: boolean}) {
|
||||
const t = useTheme()
|
||||
|
||||
let opacity: number
|
||||
switch (t.name) {
|
||||
case 'dark':
|
||||
opacity = 0.4
|
||||
break
|
||||
case 'dim':
|
||||
opacity = 0.45
|
||||
break
|
||||
case 'light':
|
||||
opacity = 0.5
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.pointer_events_none,
|
||||
a.transition_opacity,
|
||||
t.atoms.bg_contrast_25,
|
||||
style,
|
||||
{opacity: hover ? opacity : 0},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+109
-38
@@ -7,6 +7,7 @@ import {
|
||||
} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
@@ -22,9 +23,19 @@ import {
|
||||
import {useGetPopularFeedsQuery} from '#/state/queries/feed'
|
||||
import {Nux, useNux} from '#/state/queries/nuxs'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useGetSuggestedFeedsQuery} from '#/state/queries/trending/useGetSuggestedFeedsQuery'
|
||||
import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery'
|
||||
import {useSuggestedStarterPacksQuery} from '#/state/queries/useSuggestedStarterPacksQuery'
|
||||
import {
|
||||
createGetSuggestedFeedsQueryKey,
|
||||
useGetSuggestedFeedsQuery,
|
||||
} from '#/state/queries/trending/useGetSuggestedFeedsQuery'
|
||||
import {
|
||||
getSuggestedUsersQueryKeyRoot,
|
||||
useGetSuggestedUsersQuery,
|
||||
} from '#/state/queries/trending/useGetSuggestedUsersQuery'
|
||||
import {createGetTrendsQueryKey} from '#/state/queries/trending/useGetTrendsQuery'
|
||||
import {
|
||||
createSuggestedStarterPacksQueryKey,
|
||||
useSuggestedStarterPacksQuery,
|
||||
} from '#/state/queries/useSuggestedStarterPacksQuery'
|
||||
import {useProgressGuide} from '#/state/shell/progress-guide'
|
||||
import {isThreadChildAt, isThreadParentAt} from '#/view/com/posts/PostFeed'
|
||||
import {PostFeedItem} from '#/view/com/posts/PostFeedItem'
|
||||
@@ -41,6 +52,7 @@ import {ExploreRecommendations} from '#/screens/Search/modules/ExploreRecommenda
|
||||
import {ExploreTrendingTopics} from '#/screens/Search/modules/ExploreTrendingTopics'
|
||||
import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVideos'
|
||||
import {atoms as a, native, platform, useTheme, web} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Button} from '#/components/Button'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron'
|
||||
@@ -49,9 +61,11 @@ import {type Props as IcoProps} from '#/components/icons/common'
|
||||
import {type Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle'
|
||||
import {StarterPack} from '#/components/icons/StarterPack'
|
||||
import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending'
|
||||
import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as ModuleHeader from './components/ModuleHeader'
|
||||
import {
|
||||
@@ -69,33 +83,26 @@ function LoadMore({item}: {item: ExploreScreenItems & {type: 'loadMore'}}) {
|
||||
onPress={item.onLoadMore}
|
||||
style={[a.relative, a.w_full]}>
|
||||
{({hovered, pressed}) => (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
a.gap_sm,
|
||||
(hovered || pressed) && t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Text
|
||||
<>
|
||||
<SubtleHover hover={hovered || pressed} />
|
||||
<View
|
||||
style={[
|
||||
a.leading_snug,
|
||||
hovered ? t.atoms.text : t.atoms.text_contrast_medium,
|
||||
a.flex_1,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
a.gap_sm,
|
||||
]}>
|
||||
{item.message}
|
||||
</Text>
|
||||
{item.isLoadingMore ? (
|
||||
<Loader size="sm" />
|
||||
) : (
|
||||
<ChevronDownIcon
|
||||
size="sm"
|
||||
style={hovered ? t.atoms.text : t.atoms.text_contrast_medium}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Text style={[a.leading_snug]}>{item.message}</Text>
|
||||
{item.isLoadingMore ? (
|
||||
<Loader size="sm" />
|
||||
) : (
|
||||
<ChevronDownIcon size="sm" style={t.atoms.text_contrast_medium} />
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
@@ -112,6 +119,7 @@ type ExploreScreenItems =
|
||||
title: string
|
||||
icon: React.ComponentType<SVGIconProps>
|
||||
iconSize?: IcoProps['size']
|
||||
bottomBorder?: boolean
|
||||
searchButton?: {
|
||||
label: string
|
||||
metricsTag: MetricEvents['explore:module:searchButtonPress']['module']
|
||||
@@ -147,6 +155,10 @@ type ExploreScreenItems =
|
||||
profile: AppBskyActorDefs.ProfileViewBasic
|
||||
recId?: number
|
||||
}
|
||||
| {
|
||||
type: 'profileEmpty'
|
||||
key: 'profileEmpty'
|
||||
}
|
||||
| {
|
||||
type: 'feed'
|
||||
key: string
|
||||
@@ -203,11 +215,12 @@ export function Explore({
|
||||
const gate = useGate()
|
||||
const guide = useProgressGuide('follow-10')
|
||||
const [selectedInterest, setSelectedInterest] = useState<string | null>(null)
|
||||
// TODO always get at least 10 back
|
||||
// TODO always get at least 10 back TODO still
|
||||
const {
|
||||
data: suggestedUsers,
|
||||
isLoading: suggestedUsersIsLoading,
|
||||
error: suggestedUsersError,
|
||||
isRefetching: suggestedUsersIsRefetching,
|
||||
} = useGetSuggestedUsersQuery({
|
||||
category: selectedInterest,
|
||||
})
|
||||
@@ -227,6 +240,7 @@ export function Explore({
|
||||
data: suggestedSPs,
|
||||
isLoading: isLoadingSuggestedSPs,
|
||||
error: suggestedSPsError,
|
||||
isRefetching: isRefetchingSuggestedSPs,
|
||||
} = useSuggestedStarterPacksQuery()
|
||||
|
||||
const isLoadingMoreFeeds = isFetchingNextFeedsPage && !isLoadingFeeds
|
||||
@@ -262,6 +276,27 @@ export function Explore({
|
||||
},
|
||||
} = useFeedPreviews(suggestedFeeds?.feeds ?? [])
|
||||
|
||||
const qc = useQueryClient()
|
||||
const [isPTR, setIsPTR] = useState(false)
|
||||
const onPTR = useCallback(async () => {
|
||||
setIsPTR(true)
|
||||
await Promise.all([
|
||||
await qc.resetQueries({
|
||||
queryKey: createGetTrendsQueryKey(),
|
||||
}),
|
||||
await qc.resetQueries({
|
||||
queryKey: createSuggestedStarterPacksQueryKey(),
|
||||
}),
|
||||
await qc.resetQueries({
|
||||
queryKey: [getSuggestedUsersQueryKeyRoot],
|
||||
}),
|
||||
await qc.resetQueries({
|
||||
queryKey: createGetSuggestedFeedsQueryKey(),
|
||||
}),
|
||||
])
|
||||
setIsPTR(false)
|
||||
}, [qc, setIsPTR])
|
||||
|
||||
const onLoadMoreFeedPreviews = useCallback(async () => {
|
||||
if (
|
||||
isPendingFeedPreviews ||
|
||||
@@ -305,7 +340,7 @@ export function Explore({
|
||||
},
|
||||
})
|
||||
|
||||
if (suggestedUsersIsLoading) {
|
||||
if (suggestedUsersIsLoading || suggestedUsersIsRefetching) {
|
||||
i.push({type: 'profilePlaceholder', key: 'profilePlaceholder'})
|
||||
} else if (suggestedUsersError) {
|
||||
i.push({
|
||||
@@ -333,14 +368,18 @@ export function Explore({
|
||||
}
|
||||
|
||||
if (profileItems.length === 0) {
|
||||
// no items! remove the header
|
||||
i.pop()
|
||||
i.push({
|
||||
type: 'profileEmpty',
|
||||
key: 'profileEmpty',
|
||||
})
|
||||
} else {
|
||||
i.push(...profileItems)
|
||||
}
|
||||
} else {
|
||||
// no items! remove the header
|
||||
i.pop()
|
||||
i.push({
|
||||
type: 'profileEmpty',
|
||||
key: 'profileEmpty',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
i.push({type: 'profilePlaceholder', key: 'profilePlaceholder'})
|
||||
@@ -352,6 +391,7 @@ export function Explore({
|
||||
moderationOpts,
|
||||
suggestedUsers,
|
||||
suggestedUsersIsLoading,
|
||||
suggestedUsersIsRefetching,
|
||||
suggestedUsersError,
|
||||
])
|
||||
const suggestedFeedsModule = useMemo(() => {
|
||||
@@ -466,7 +506,7 @@ export function Explore({
|
||||
iconSize: 'xl',
|
||||
})
|
||||
|
||||
if (isLoadingSuggestedSPs) {
|
||||
if (isLoadingSuggestedSPs || isRefetchingSuggestedSPs) {
|
||||
Array.from({length: 3}).forEach((__, index) =>
|
||||
i.push({
|
||||
type: 'starterPackSkeleton',
|
||||
@@ -486,7 +526,13 @@ export function Explore({
|
||||
})
|
||||
}
|
||||
return i
|
||||
}, [suggestedSPs, _, isLoadingSuggestedSPs, suggestedSPsError])
|
||||
}, [
|
||||
suggestedSPs,
|
||||
_,
|
||||
isLoadingSuggestedSPs,
|
||||
suggestedSPsError,
|
||||
isRefetchingSuggestedSPs,
|
||||
])
|
||||
const feedPreviewsModule = useMemo(() => {
|
||||
const i: ExploreScreenItems[] = []
|
||||
i.push(...feedPreviewSlices)
|
||||
@@ -520,6 +566,13 @@ export function Explore({
|
||||
if (isNewUser) {
|
||||
i.push(...suggestedFollowsModule)
|
||||
i.push(...suggestedStarterPacksModule)
|
||||
i.push({
|
||||
type: 'header',
|
||||
key: 'trending-topics-header',
|
||||
title: _(msg`Trending topics`),
|
||||
icon: Graph,
|
||||
bottomBorder: true,
|
||||
})
|
||||
i.push(trendingTopicsModule)
|
||||
} else {
|
||||
i.push(trendingTopicsModule)
|
||||
@@ -533,6 +586,7 @@ export function Explore({
|
||||
|
||||
return i
|
||||
}, [
|
||||
_,
|
||||
topBorder,
|
||||
isNewUser,
|
||||
suggestedFollowsModule,
|
||||
@@ -564,7 +618,7 @@ export function Explore({
|
||||
)
|
||||
case 'header': {
|
||||
return (
|
||||
<ModuleHeader.Container>
|
||||
<ModuleHeader.Container bottomBorder={item.bottomBorder}>
|
||||
<ModuleHeader.Icon icon={item.icon} size={item.iconSize} />
|
||||
<ModuleHeader.TitleText>{item.title}</ModuleHeader.TitleText>
|
||||
{item.searchButton && (
|
||||
@@ -623,6 +677,15 @@ export function Explore({
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'profileEmpty': {
|
||||
return (
|
||||
<View style={[a.px_lg, a.pb_lg]}>
|
||||
<Admonition>
|
||||
<Trans>No results for "{selectedInterest}".</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
case 'feed': {
|
||||
return (
|
||||
<View
|
||||
@@ -738,7 +801,13 @@ export function Explore({
|
||||
return (
|
||||
<ModuleHeader.Container
|
||||
headerHeight={headerHeight}
|
||||
style={[a.pt_xs, a.border_b, t.atoms.border_contrast_low]}>
|
||||
style={[
|
||||
a.pt_xs,
|
||||
t.atoms.border_contrast_low,
|
||||
native(a.border_b),
|
||||
]}>
|
||||
{/* Very non-scientific way to avoid small gap on scroll */}
|
||||
<View style={[a.absolute, a.inset_0, t.atoms.bg, {top: -2}]} />
|
||||
<ModuleHeader.FeedLink feed={item.feed}>
|
||||
<ModuleHeader.FeedAvatar feed={item.feed} />
|
||||
<View style={[a.flex_1, a.gap_xs]}>
|
||||
@@ -876,6 +945,8 @@ export function Explore({
|
||||
windowSize={9}
|
||||
maxToRenderPerBatch={platform({ios: 5, default: 1})}
|
||||
updateCellsBatchingPeriod={40}
|
||||
refreshing={isPTR}
|
||||
onRefresh={onPTR}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,12 @@ export function Container({
|
||||
style,
|
||||
children,
|
||||
headerHeight,
|
||||
}: {children: React.ReactNode; headerHeight?: number} & ViewStyleProp) {
|
||||
bottomBorder,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
headerHeight?: number
|
||||
bottomBorder?: boolean
|
||||
} & ViewStyleProp) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
@@ -31,10 +36,9 @@ export function Container({
|
||||
a.gap_sm,
|
||||
t.atoms.bg,
|
||||
headerHeight && web({position: 'sticky', top: headerHeight}),
|
||||
bottomBorder && [a.border_b, t.atoms.border_contrast_low],
|
||||
style,
|
||||
]}>
|
||||
{/* Very non-scientific way to avoid small gap on scroll */}
|
||||
<View style={[a.absolute, a.inset_0, t.atoms.bg, {top: -2}]} />
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus
|
||||
import {Link} from '#/components/Link'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
import {useStarterPackLink} from '#/components/StarterPack/StarterPackCard'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
@@ -48,75 +49,80 @@ export function StarterPackCard({
|
||||
.map(item => item.subject)
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.p_lg,
|
||||
a.gap_md,
|
||||
a.border,
|
||||
a.rounded_sm,
|
||||
a.overflow_hidden,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<View aria-hidden style={[a.absolute, a.inset_0, a.z_40]}>
|
||||
<Link
|
||||
to={link.to}
|
||||
label={link.label}
|
||||
style={[a.absolute, a.inset_0]}
|
||||
onHoverIn={link.precache}
|
||||
onPress={link.precache}>
|
||||
<View />
|
||||
</Link>
|
||||
</View>
|
||||
<Link
|
||||
to={link.to}
|
||||
label={link.label}
|
||||
onHoverIn={link.precache}
|
||||
onPress={link.precache}>
|
||||
{s => (
|
||||
<>
|
||||
<SubtleHover hover={s.hovered || s.pressed} />
|
||||
|
||||
<AvatarStack
|
||||
profiles={profiles ?? []}
|
||||
numPending={profileCount}
|
||||
total={view.list?.listItemCount}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.p_lg,
|
||||
a.gap_md,
|
||||
a.border,
|
||||
a.rounded_sm,
|
||||
a.overflow_hidden,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<AvatarStack
|
||||
profiles={profiles ?? []}
|
||||
numPending={profileCount}
|
||||
total={view.list?.listItemCount}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_start,
|
||||
a.gap_lg,
|
||||
web({
|
||||
position: 'static',
|
||||
zIndex: 'unset',
|
||||
}),
|
||||
]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}
|
||||
numberOfLines={1}>
|
||||
{view.record.name}
|
||||
</Text>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}
|
||||
numberOfLines={1}>
|
||||
{view.creator?.did === currentAccount?.did
|
||||
? _(msg`By you`)
|
||||
: _(msg`By ${sanitizeHandle(view.creator.handle, '@')}`)}
|
||||
</Text>
|
||||
</View>
|
||||
<Link
|
||||
to={link.to}
|
||||
label={link.label}
|
||||
onHoverIn={link.precache}
|
||||
onPress={link.precache}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="small"
|
||||
style={[a.z_50]}>
|
||||
<ButtonText>
|
||||
<Trans>Open pack</Trans>
|
||||
</ButtonText>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_start,
|
||||
a.gap_lg,
|
||||
web({
|
||||
position: 'static',
|
||||
zIndex: 'unset',
|
||||
}),
|
||||
]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}
|
||||
numberOfLines={1}>
|
||||
{view.record.name}
|
||||
</Text>
|
||||
<Text
|
||||
emoji
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.leading_snug,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}
|
||||
numberOfLines={1}>
|
||||
{view.creator?.did === currentAccount?.did
|
||||
? _(msg`By you`)
|
||||
: _(msg`By ${sanitizeHandle(view.creator.handle, '@')}`)}
|
||||
</Text>
|
||||
</View>
|
||||
<Link
|
||||
to={link.to}
|
||||
label={link.label}
|
||||
onHoverIn={link.precache}
|
||||
onPress={link.precache}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="small"
|
||||
style={[a.z_50]}>
|
||||
<ButtonText>
|
||||
<Trans>Open pack</Trans>
|
||||
</ButtonText>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -40,14 +40,14 @@ export function ExploreInterestsCard() {
|
||||
<>
|
||||
<Prompt.Basic
|
||||
control={trendingPrompt}
|
||||
title={_(msg`Your interests`)}
|
||||
title={_(msg`Dismiss interests`)}
|
||||
description={_(
|
||||
msg`You can adjust your interests at any time from your "Content and media" settings.`,
|
||||
msg`You can adjust your interests at any time from "Content and media" settings.`,
|
||||
)}
|
||||
confirmButtonCta={_(
|
||||
msg({
|
||||
message: `Copy that!`,
|
||||
comment: `Confirm button text. Can be a short cheeky phrase that means "OK" e.g. "Copy that!"`,
|
||||
message: `OK`,
|
||||
comment: `Confirm button text.`,
|
||||
}),
|
||||
)}
|
||||
onConfirm={onConfirmClose}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {atoms as a} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {boostInterests, Tabs} from '#/components/ProgressGuide/FollowDialog'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
@@ -133,10 +134,7 @@ let Tab = ({
|
||||
a.py_sm,
|
||||
a.border,
|
||||
active || hovered || pressed || focused
|
||||
? [
|
||||
t.atoms.bg_contrast_25,
|
||||
{borderColor: t.atoms.bg_contrast_25.backgroundColor},
|
||||
]
|
||||
? [t.atoms.bg_contrast_25, t.atoms.border_contrast_medium]
|
||||
: [t.atoms.bg, t.atoms.border_contrast_low],
|
||||
]}>
|
||||
<Text
|
||||
@@ -186,47 +184,52 @@ let SuggestedProfileCard = ({
|
||||
{statsig: true},
|
||||
)
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.py_lg,
|
||||
a.px_lg,
|
||||
a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
a.flex_1,
|
||||
]}>
|
||||
<ProfileCard.Outer>
|
||||
<ProfileCard.Header>
|
||||
<ProfileCard.Avatar
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
<ProfileCard.NameAndHandle
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
<ProfileCard.FollowButton
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
withIcon={false}
|
||||
logContext="ExploreSuggestedAccounts"
|
||||
onFollow={() => {
|
||||
logger.metric(
|
||||
'suggestedUser:follow',
|
||||
{
|
||||
logContext: 'Explore',
|
||||
location: 'Card',
|
||||
recId,
|
||||
position,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</ProfileCard.Header>
|
||||
<ProfileCard.Description profile={profile} numberOfLines={2} />
|
||||
</ProfileCard.Outer>
|
||||
</View>
|
||||
{s => (
|
||||
<>
|
||||
<SubtleHover hover={s.hovered || s.pressed} />
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.w_full,
|
||||
a.py_lg,
|
||||
a.px_lg,
|
||||
a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<ProfileCard.Outer>
|
||||
<ProfileCard.Header>
|
||||
<ProfileCard.Avatar
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
<ProfileCard.NameAndHandle
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
<ProfileCard.FollowButton
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
withIcon={false}
|
||||
logContext="ExploreSuggestedAccounts"
|
||||
onFollow={() => {
|
||||
logger.metric(
|
||||
'suggestedUser:follow',
|
||||
{
|
||||
logContext: 'Explore',
|
||||
location: 'Card',
|
||||
recId,
|
||||
position,
|
||||
},
|
||||
{statsig: true},
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</ProfileCard.Header>
|
||||
<ProfileCard.Description profile={profile} numberOfLines={2} />
|
||||
</ProfileCard.Outer>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</ProfileCard.Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {type Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {Flame_Stroke2_Corner1_Rounded as FlameIcon} from '#/components/icons/Flame'
|
||||
import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/icons/Trending'
|
||||
import {Link} from '#/components/Link'
|
||||
import {SubtleHover} from '#/components/SubtleHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
const TOPIC_COUNT = 5
|
||||
@@ -28,10 +29,10 @@ export function ExploreTrendingTopics() {
|
||||
}
|
||||
|
||||
function Inner() {
|
||||
const {data: trending, error, isLoading} = useGetTrendsQuery()
|
||||
const {data: trending, error, isLoading, isRefetching} = useGetTrendsQuery()
|
||||
const noTopics = !isLoading && !error && !trending?.trends?.length
|
||||
|
||||
return isLoading ? (
|
||||
return isLoading || isRefetching ? (
|
||||
Array.from({length: TOPIC_COUNT}).map((__, i) => (
|
||||
<TrendingTopicRowSkeleton key={i} withPosts={i === 0} />
|
||||
))
|
||||
@@ -92,25 +93,23 @@ export function TrendRow({
|
||||
PressableComponent={Pressable}>
|
||||
{({hovered, pressed}) => (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
gutters,
|
||||
a.w_full,
|
||||
a.py_lg,
|
||||
a.flex_row,
|
||||
a.gap_2xs,
|
||||
(hovered || pressed) && t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<SubtleHover hover={hovered || pressed} />
|
||||
<View style={[gutters, a.w_full, a.py_lg, a.flex_row, a.gap_2xs]}>
|
||||
<View style={[a.flex_1, a.gap_xs]}>
|
||||
<View style={[a.flex_row]}>
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, a.leading_snug, {width: 20}]}>
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.leading_tight,
|
||||
{width: 20},
|
||||
]}>
|
||||
<Trans comment='The trending topic rank, i.e. "1. March Madness", "2. The Bachelor"'>
|
||||
{rank}.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}
|
||||
style={[a.text_md, a.font_bold, a.leading_tight]}
|
||||
numberOfLines={1}>
|
||||
{trend.displayName}
|
||||
</Text>
|
||||
|
||||
@@ -34,6 +34,41 @@ const RQKEY_ROOT = 'feed-previews'
|
||||
const RQKEY = (feeds: string[]) => [RQKEY_ROOT, feeds]
|
||||
|
||||
const LIMIT = 8 // sliced to 6, overfetch to account for moderation
|
||||
const PINNED_POST_URIS: Record<string, boolean> = {
|
||||
// 📰 News
|
||||
'at://did:plc:kkf4naxqmweop7dv4l2iqqf5/app.bsky.feed.post/3lgh27w2ngc2b':
|
||||
true,
|
||||
// Gardening
|
||||
'at://did:plc:5rw2on4i56btlcajojaxwcat/app.bsky.feed.post/3kjorckgcwc27':
|
||||
true,
|
||||
// Web Development Trending
|
||||
'at://did:plc:m2sjv3wncvsasdapla35hzwj/app.bsky.feed.post/3lfaw445axs22':
|
||||
true,
|
||||
// Anime & Manga EN
|
||||
'at://did:plc:tazrmeme4dzahimsykusrwrk/app.bsky.feed.post/3knxx2gmkns2y':
|
||||
true,
|
||||
// 📽️ Film
|
||||
'at://did:plc:2hwwem55ce6djnk6bn62cstr/app.bsky.feed.post/3llhpzhbq7c2g':
|
||||
true,
|
||||
// PopSky
|
||||
'at://did:plc:lfdf4srj43iwdng7jn35tjsp/app.bsky.feed.post/3lbblgly65c2g':
|
||||
true,
|
||||
// Science
|
||||
'at://did:plc:hu2obebw3nhfj667522dahfg/app.bsky.feed.post/3kl33otd6ob2s':
|
||||
true,
|
||||
// Birds! 🦉
|
||||
'at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.post/3lbg4r57yk22d':
|
||||
true,
|
||||
// Astronomy
|
||||
'at://did:plc:xy2zorw2ys47poflotxthlzg/app.bsky.feed.post/3kyzye4lujs2w':
|
||||
true,
|
||||
// What's Cooking 🍽️
|
||||
'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3lfqhgvxbqc2q':
|
||||
true,
|
||||
// BookSky 💙📚 #booksky
|
||||
'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3kgrm2rw5ww2e':
|
||||
true,
|
||||
}
|
||||
|
||||
export type FeedPreviewItem =
|
||||
| {
|
||||
@@ -181,19 +216,24 @@ export function useFeedPreviews(
|
||||
feedContext: item.feedContext,
|
||||
reason: item.reason,
|
||||
feedPostUri: item.feedPostUri,
|
||||
items: item.items.slice(0, 6).map((subItem, i) => {
|
||||
const feedPostSliceItem: FeedPostSliceItem = {
|
||||
_reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`,
|
||||
uri: subItem.post.uri,
|
||||
post: subItem.post,
|
||||
record: subItem.record,
|
||||
moderation: moderations[i],
|
||||
parentAuthor: subItem.parentAuthor,
|
||||
isParentBlocked: subItem.isParentBlocked,
|
||||
isParentNotFound: subItem.isParentNotFound,
|
||||
}
|
||||
return feedPostSliceItem
|
||||
}),
|
||||
items: item.items
|
||||
.slice(0, 6)
|
||||
.filter(subItem => {
|
||||
return !PINNED_POST_URIS[subItem.post.uri]
|
||||
})
|
||||
.map((subItem, i) => {
|
||||
const feedPostSliceItem: FeedPostSliceItem = {
|
||||
_reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`,
|
||||
uri: subItem.post.uri,
|
||||
post: subItem.post,
|
||||
record: subItem.record,
|
||||
moderation: moderations[i],
|
||||
parentAuthor: subItem.parentAuthor,
|
||||
isParentBlocked: subItem.isParentBlocked,
|
||||
isParentNotFound: subItem.isParentNotFound,
|
||||
}
|
||||
return feedPostSliceItem
|
||||
}),
|
||||
}
|
||||
if (slice.isIncompleteThread && slice.items.length >= 3) {
|
||||
const beforeLast = slice.items.length - 2
|
||||
|
||||
@@ -20,8 +20,7 @@ export function useGetSuggestedFeedsQuery() {
|
||||
|
||||
return useQuery({
|
||||
enabled: !!preferences,
|
||||
refetchOnWindowFocus: true,
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
staleTime: STALE.MINUTES.THREE,
|
||||
queryKey: createGetSuggestedFeedsQueryKey(),
|
||||
queryFn: async () => {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
|
||||
@@ -27,14 +27,14 @@ export function useGetSuggestedUsersQuery(props: QueryProps) {
|
||||
|
||||
return useQuery({
|
||||
enabled: !!preferences,
|
||||
refetchOnWindowFocus: true,
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
staleTime: STALE.MINUTES.THREE,
|
||||
queryKey: createGetSuggestedUsersQueryKey(props),
|
||||
queryFn: async () => {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const {data} = await agent.app.bsky.unspecced.getSuggestedUsers(
|
||||
{
|
||||
category: props.category ?? undefined,
|
||||
limit: 10,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
|
||||
@@ -25,7 +25,6 @@ export function useGetTrendsQuery() {
|
||||
|
||||
return useQuery({
|
||||
enabled: !!preferences,
|
||||
refetchOnWindowFocus: true,
|
||||
staleTime: STALE.MINUTES.THREE,
|
||||
queryKey: createGetTrendsQueryKey(),
|
||||
queryFn: async () => {
|
||||
|
||||
@@ -20,8 +20,7 @@ export function useSuggestedStarterPacksQuery() {
|
||||
|
||||
return useQuery({
|
||||
enabled: !!preferences,
|
||||
refetchOnWindowFocus: true,
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
staleTime: STALE.MINUTES.THREE,
|
||||
queryKey: createSuggestedStarterPacksQueryKey(),
|
||||
async queryFn() {
|
||||
const {data} = await agent.app.bsky.unspecced.getSuggestedStarterPacks(
|
||||
|
||||
Reference in New Issue
Block a user