mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[Explore] Small fixes (#8145)

* Hover on sugg account

* Add subtle hover to all components except feed

* Use skeleton states for refetch on focus

* Empty results state for sugg users

* Filter out pinned posts from feed previews

* Add trending header if not top module

* Tighten up spacing

* Fetch 10 profiles

* Update interests copy

* Remove refetch on focus

* Add PTR

* use a map

* Update src/screens/Search/modules/ExploreInterestsCard.tsx

* fix web double border

---------

Co-authored-by: Hailey <me@haileyok.com>

authored by

Eric Bailey
Hailey
and committed by
GitHub
4013855c e46b78eb

+344 -190
+34
src/components/SubtleHover.tsx
··· 1 + import {View} from 'react-native' 2 + 3 + import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 4 + 5 + export function SubtleHover({style, hover}: ViewStyleProp & {hover: boolean}) { 6 + const t = useTheme() 7 + 8 + let opacity: number 9 + switch (t.name) { 10 + case 'dark': 11 + opacity = 0.4 12 + break 13 + case 'dim': 14 + opacity = 0.45 15 + break 16 + case 'light': 17 + opacity = 0.5 18 + break 19 + } 20 + 21 + return ( 22 + <View 23 + style={[ 24 + a.absolute, 25 + a.inset_0, 26 + a.pointer_events_none, 27 + a.transition_opacity, 28 + t.atoms.bg_contrast_25, 29 + style, 30 + {opacity: hover ? opacity : 0}, 31 + ]} 32 + /> 33 + ) 34 + }
+109 -38
src/screens/Search/Explore.tsx
··· 7 7 } from '@atproto/api' 8 8 import {msg, Trans} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 + import {useQueryClient} from '@tanstack/react-query' 10 11 11 12 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 12 13 import {useGate} from '#/lib/statsig/statsig' ··· 22 23 import {useGetPopularFeedsQuery} from '#/state/queries/feed' 23 24 import {Nux, useNux} from '#/state/queries/nuxs' 24 25 import {usePreferencesQuery} from '#/state/queries/preferences' 25 - import {useGetSuggestedFeedsQuery} from '#/state/queries/trending/useGetSuggestedFeedsQuery' 26 - import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery' 27 - import {useSuggestedStarterPacksQuery} from '#/state/queries/useSuggestedStarterPacksQuery' 26 + import { 27 + createGetSuggestedFeedsQueryKey, 28 + useGetSuggestedFeedsQuery, 29 + } from '#/state/queries/trending/useGetSuggestedFeedsQuery' 30 + import { 31 + getSuggestedUsersQueryKeyRoot, 32 + useGetSuggestedUsersQuery, 33 + } from '#/state/queries/trending/useGetSuggestedUsersQuery' 34 + import {createGetTrendsQueryKey} from '#/state/queries/trending/useGetTrendsQuery' 35 + import { 36 + createSuggestedStarterPacksQueryKey, 37 + useSuggestedStarterPacksQuery, 38 + } from '#/state/queries/useSuggestedStarterPacksQuery' 28 39 import {useProgressGuide} from '#/state/shell/progress-guide' 29 40 import {isThreadChildAt, isThreadParentAt} from '#/view/com/posts/PostFeed' 30 41 import {PostFeedItem} from '#/view/com/posts/PostFeedItem' ··· 41 52 import {ExploreTrendingTopics} from '#/screens/Search/modules/ExploreTrendingTopics' 42 53 import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVideos' 43 54 import {atoms as a, native, platform, useTheme, web} from '#/alf' 55 + import {Admonition} from '#/components/Admonition' 44 56 import {Button} from '#/components/Button' 45 57 import * as FeedCard from '#/components/FeedCard' 46 58 import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron' ··· 49 61 import {type Props as SVGIconProps} from '#/components/icons/common' 50 62 import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' 51 63 import {StarterPack} from '#/components/icons/StarterPack' 64 + import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending' 52 65 import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' 53 66 import {Loader} from '#/components/Loader' 54 67 import * as ProfileCard from '#/components/ProfileCard' 68 + import {SubtleHover} from '#/components/SubtleHover' 55 69 import {Text} from '#/components/Typography' 56 70 import * as ModuleHeader from './components/ModuleHeader' 57 71 import { ··· 69 83 onPress={item.onLoadMore} 70 84 style={[a.relative, a.w_full]}> 71 85 {({hovered, pressed}) => ( 72 - <View 73 - style={[ 74 - a.flex_1, 75 - a.flex_row, 76 - a.align_center, 77 - a.justify_center, 78 - a.px_lg, 79 - a.py_md, 80 - a.gap_sm, 81 - (hovered || pressed) && t.atoms.bg_contrast_25, 82 - ]}> 83 - <Text 86 + <> 87 + <SubtleHover hover={hovered || pressed} /> 88 + <View 84 89 style={[ 85 - a.leading_snug, 86 - hovered ? t.atoms.text : t.atoms.text_contrast_medium, 90 + a.flex_1, 91 + a.flex_row, 92 + a.align_center, 93 + a.justify_center, 94 + a.px_lg, 95 + a.py_md, 96 + a.gap_sm, 87 97 ]}> 88 - {item.message} 89 - </Text> 90 - {item.isLoadingMore ? ( 91 - <Loader size="sm" /> 92 - ) : ( 93 - <ChevronDownIcon 94 - size="sm" 95 - style={hovered ? t.atoms.text : t.atoms.text_contrast_medium} 96 - /> 97 - )} 98 - </View> 98 + <Text style={[a.leading_snug]}>{item.message}</Text> 99 + {item.isLoadingMore ? ( 100 + <Loader size="sm" /> 101 + ) : ( 102 + <ChevronDownIcon size="sm" style={t.atoms.text_contrast_medium} /> 103 + )} 104 + </View> 105 + </> 99 106 )} 100 107 </Button> 101 108 ) ··· 112 119 title: string 113 120 icon: React.ComponentType<SVGIconProps> 114 121 iconSize?: IcoProps['size'] 122 + bottomBorder?: boolean 115 123 searchButton?: { 116 124 label: string 117 125 metricsTag: MetricEvents['explore:module:searchButtonPress']['module'] ··· 148 156 recId?: number 149 157 } 150 158 | { 159 + type: 'profileEmpty' 160 + key: 'profileEmpty' 161 + } 162 + | { 151 163 type: 'feed' 152 164 key: string 153 165 feed: AppBskyFeedDefs.GeneratorView ··· 203 215 const gate = useGate() 204 216 const guide = useProgressGuide('follow-10') 205 217 const [selectedInterest, setSelectedInterest] = useState<string | null>(null) 206 - // TODO always get at least 10 back 218 + // TODO always get at least 10 back TODO still 207 219 const { 208 220 data: suggestedUsers, 209 221 isLoading: suggestedUsersIsLoading, 210 222 error: suggestedUsersError, 223 + isRefetching: suggestedUsersIsRefetching, 211 224 } = useGetSuggestedUsersQuery({ 212 225 category: selectedInterest, 213 226 }) ··· 227 240 data: suggestedSPs, 228 241 isLoading: isLoadingSuggestedSPs, 229 242 error: suggestedSPsError, 243 + isRefetching: isRefetchingSuggestedSPs, 230 244 } = useSuggestedStarterPacksQuery() 231 245 232 246 const isLoadingMoreFeeds = isFetchingNextFeedsPage && !isLoadingFeeds ··· 262 276 }, 263 277 } = useFeedPreviews(suggestedFeeds?.feeds ?? []) 264 278 279 + const qc = useQueryClient() 280 + const [isPTR, setIsPTR] = useState(false) 281 + const onPTR = useCallback(async () => { 282 + setIsPTR(true) 283 + await Promise.all([ 284 + await qc.resetQueries({ 285 + queryKey: createGetTrendsQueryKey(), 286 + }), 287 + await qc.resetQueries({ 288 + queryKey: createSuggestedStarterPacksQueryKey(), 289 + }), 290 + await qc.resetQueries({ 291 + queryKey: [getSuggestedUsersQueryKeyRoot], 292 + }), 293 + await qc.resetQueries({ 294 + queryKey: createGetSuggestedFeedsQueryKey(), 295 + }), 296 + ]) 297 + setIsPTR(false) 298 + }, [qc, setIsPTR]) 299 + 265 300 const onLoadMoreFeedPreviews = useCallback(async () => { 266 301 if ( 267 302 isPendingFeedPreviews || ··· 305 340 }, 306 341 }) 307 342 308 - if (suggestedUsersIsLoading) { 343 + if (suggestedUsersIsLoading || suggestedUsersIsRefetching) { 309 344 i.push({type: 'profilePlaceholder', key: 'profilePlaceholder'}) 310 345 } else if (suggestedUsersError) { 311 346 i.push({ ··· 333 368 } 334 369 335 370 if (profileItems.length === 0) { 336 - // no items! remove the header 337 - i.pop() 371 + i.push({ 372 + type: 'profileEmpty', 373 + key: 'profileEmpty', 374 + }) 338 375 } else { 339 376 i.push(...profileItems) 340 377 } 341 378 } else { 342 - // no items! remove the header 343 - i.pop() 379 + i.push({ 380 + type: 'profileEmpty', 381 + key: 'profileEmpty', 382 + }) 344 383 } 345 384 } else { 346 385 i.push({type: 'profilePlaceholder', key: 'profilePlaceholder'}) ··· 352 391 moderationOpts, 353 392 suggestedUsers, 354 393 suggestedUsersIsLoading, 394 + suggestedUsersIsRefetching, 355 395 suggestedUsersError, 356 396 ]) 357 397 const suggestedFeedsModule = useMemo(() => { ··· 466 506 iconSize: 'xl', 467 507 }) 468 508 469 - if (isLoadingSuggestedSPs) { 509 + if (isLoadingSuggestedSPs || isRefetchingSuggestedSPs) { 470 510 Array.from({length: 3}).forEach((__, index) => 471 511 i.push({ 472 512 type: 'starterPackSkeleton', ··· 486 526 }) 487 527 } 488 528 return i 489 - }, [suggestedSPs, _, isLoadingSuggestedSPs, suggestedSPsError]) 529 + }, [ 530 + suggestedSPs, 531 + _, 532 + isLoadingSuggestedSPs, 533 + suggestedSPsError, 534 + isRefetchingSuggestedSPs, 535 + ]) 490 536 const feedPreviewsModule = useMemo(() => { 491 537 const i: ExploreScreenItems[] = [] 492 538 i.push(...feedPreviewSlices) ··· 520 566 if (isNewUser) { 521 567 i.push(...suggestedFollowsModule) 522 568 i.push(...suggestedStarterPacksModule) 569 + i.push({ 570 + type: 'header', 571 + key: 'trending-topics-header', 572 + title: _(msg`Trending topics`), 573 + icon: Graph, 574 + bottomBorder: true, 575 + }) 523 576 i.push(trendingTopicsModule) 524 577 } else { 525 578 i.push(trendingTopicsModule) ··· 533 586 534 587 return i 535 588 }, [ 589 + _, 536 590 topBorder, 537 591 isNewUser, 538 592 suggestedFollowsModule, ··· 564 618 ) 565 619 case 'header': { 566 620 return ( 567 - <ModuleHeader.Container> 621 + <ModuleHeader.Container bottomBorder={item.bottomBorder}> 568 622 <ModuleHeader.Icon icon={item.icon} size={item.iconSize} /> 569 623 <ModuleHeader.TitleText>{item.title}</ModuleHeader.TitleText> 570 624 {item.searchButton && ( ··· 623 677 /> 624 678 ) 625 679 } 680 + case 'profileEmpty': { 681 + return ( 682 + <View style={[a.px_lg, a.pb_lg]}> 683 + <Admonition> 684 + <Trans>No results for "{selectedInterest}".</Trans> 685 + </Admonition> 686 + </View> 687 + ) 688 + } 626 689 case 'feed': { 627 690 return ( 628 691 <View ··· 738 801 return ( 739 802 <ModuleHeader.Container 740 803 headerHeight={headerHeight} 741 - style={[a.pt_xs, a.border_b, t.atoms.border_contrast_low]}> 804 + style={[ 805 + a.pt_xs, 806 + t.atoms.border_contrast_low, 807 + native(a.border_b), 808 + ]}> 809 + {/* Very non-scientific way to avoid small gap on scroll */} 810 + <View style={[a.absolute, a.inset_0, t.atoms.bg, {top: -2}]} /> 742 811 <ModuleHeader.FeedLink feed={item.feed}> 743 812 <ModuleHeader.FeedAvatar feed={item.feed} /> 744 813 <View style={[a.flex_1, a.gap_xs]}> ··· 876 945 windowSize={9} 877 946 maxToRenderPerBatch={platform({ios: 5, default: 1})} 878 947 updateCellsBatchingPeriod={40} 948 + refreshing={isPTR} 949 + onRefresh={onPTR} 879 950 /> 880 951 ) 881 952 }
+7 -3
src/screens/Search/components/ModuleHeader.tsx
··· 18 18 style, 19 19 children, 20 20 headerHeight, 21 - }: {children: React.ReactNode; headerHeight?: number} & ViewStyleProp) { 21 + bottomBorder, 22 + }: { 23 + children: React.ReactNode 24 + headerHeight?: number 25 + bottomBorder?: boolean 26 + } & ViewStyleProp) { 22 27 const t = useTheme() 23 28 return ( 24 29 <View ··· 31 36 a.gap_sm, 32 37 t.atoms.bg, 33 38 headerHeight && web({position: 'sticky', top: headerHeight}), 39 + bottomBorder && [a.border_b, t.atoms.border_contrast_low], 34 40 style, 35 41 ]}> 36 - {/* Very non-scientific way to avoid small gap on scroll */} 37 - <View style={[a.absolute, a.inset_0, t.atoms.bg, {top: -2}]} /> 38 42 {children} 39 43 </View> 40 44 )
+73 -67
src/screens/Search/components/StarterPackCard.tsx
··· 19 19 import {Link} from '#/components/Link' 20 20 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 21 21 import {useStarterPackLink} from '#/components/StarterPack/StarterPackCard' 22 + import {SubtleHover} from '#/components/SubtleHover' 22 23 import {Text} from '#/components/Typography' 23 24 import * as bsky from '#/types/bsky' 24 25 ··· 48 49 .map(item => item.subject) 49 50 50 51 return ( 51 - <View 52 - style={[ 53 - a.w_full, 54 - a.p_lg, 55 - a.gap_md, 56 - a.border, 57 - a.rounded_sm, 58 - a.overflow_hidden, 59 - t.atoms.border_contrast_low, 60 - ]}> 61 - <View aria-hidden style={[a.absolute, a.inset_0, a.z_40]}> 62 - <Link 63 - to={link.to} 64 - label={link.label} 65 - style={[a.absolute, a.inset_0]} 66 - onHoverIn={link.precache} 67 - onPress={link.precache}> 68 - <View /> 69 - </Link> 70 - </View> 52 + <Link 53 + to={link.to} 54 + label={link.label} 55 + onHoverIn={link.precache} 56 + onPress={link.precache}> 57 + {s => ( 58 + <> 59 + <SubtleHover hover={s.hovered || s.pressed} /> 71 60 72 - <AvatarStack 73 - profiles={profiles ?? []} 74 - numPending={profileCount} 75 - total={view.list?.listItemCount} 76 - /> 61 + <View 62 + style={[ 63 + a.w_full, 64 + a.p_lg, 65 + a.gap_md, 66 + a.border, 67 + a.rounded_sm, 68 + a.overflow_hidden, 69 + t.atoms.border_contrast_low, 70 + ]}> 71 + <AvatarStack 72 + profiles={profiles ?? []} 73 + numPending={profileCount} 74 + total={view.list?.listItemCount} 75 + /> 77 76 78 - <View 79 - style={[ 80 - a.w_full, 81 - a.flex_row, 82 - a.align_start, 83 - a.gap_lg, 84 - web({ 85 - position: 'static', 86 - zIndex: 'unset', 87 - }), 88 - ]}> 89 - <View style={[a.flex_1]}> 90 - <Text 91 - emoji 92 - style={[a.text_md, a.font_bold, a.leading_snug]} 93 - numberOfLines={1}> 94 - {view.record.name} 95 - </Text> 96 - <Text 97 - emoji 98 - style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]} 99 - numberOfLines={1}> 100 - {view.creator?.did === currentAccount?.did 101 - ? _(msg`By you`) 102 - : _(msg`By ${sanitizeHandle(view.creator.handle, '@')}`)} 103 - </Text> 104 - </View> 105 - <Link 106 - to={link.to} 107 - label={link.label} 108 - onHoverIn={link.precache} 109 - onPress={link.precache} 110 - variant="solid" 111 - color="secondary" 112 - size="small" 113 - style={[a.z_50]}> 114 - <ButtonText> 115 - <Trans>Open pack</Trans> 116 - </ButtonText> 117 - </Link> 118 - </View> 119 - </View> 77 + <View 78 + style={[ 79 + a.w_full, 80 + a.flex_row, 81 + a.align_start, 82 + a.gap_lg, 83 + web({ 84 + position: 'static', 85 + zIndex: 'unset', 86 + }), 87 + ]}> 88 + <View style={[a.flex_1]}> 89 + <Text 90 + emoji 91 + style={[a.text_md, a.font_bold, a.leading_snug]} 92 + numberOfLines={1}> 93 + {view.record.name} 94 + </Text> 95 + <Text 96 + emoji 97 + style={[ 98 + a.text_sm, 99 + a.leading_snug, 100 + t.atoms.text_contrast_medium, 101 + ]} 102 + numberOfLines={1}> 103 + {view.creator?.did === currentAccount?.did 104 + ? _(msg`By you`) 105 + : _(msg`By ${sanitizeHandle(view.creator.handle, '@')}`)} 106 + </Text> 107 + </View> 108 + <Link 109 + to={link.to} 110 + label={link.label} 111 + onHoverIn={link.precache} 112 + onPress={link.precache} 113 + variant="solid" 114 + color="secondary" 115 + size="small" 116 + style={[a.z_50]}> 117 + <ButtonText> 118 + <Trans>Open pack</Trans> 119 + </ButtonText> 120 + </Link> 121 + </View> 122 + </View> 123 + </> 124 + )} 125 + </Link> 120 126 ) 121 127 } 122 128
+4 -4
src/screens/Search/modules/ExploreInterestsCard.tsx
··· 40 40 <> 41 41 <Prompt.Basic 42 42 control={trendingPrompt} 43 - title={_(msg`Your interests`)} 43 + title={_(msg`Dismiss interests`)} 44 44 description={_( 45 - msg`You can adjust your interests at any time from your "Content and media" settings.`, 45 + msg`You can adjust your interests at any time from "Content and media" settings.`, 46 46 )} 47 47 confirmButtonCta={_( 48 48 msg({ 49 - message: `Copy that!`, 50 - comment: `Confirm button text. Can be a short cheeky phrase that means "OK" e.g. "Copy that!"`, 49 + message: `OK`, 50 + comment: `Confirm button text.`, 51 51 }), 52 52 )} 53 53 onConfirm={onConfirmClose}
+48 -45
src/screens/Search/modules/ExploreSuggestedAccounts.tsx
··· 17 17 import {Button} from '#/components/Button' 18 18 import * as ProfileCard from '#/components/ProfileCard' 19 19 import {boostInterests, Tabs} from '#/components/ProgressGuide/FollowDialog' 20 + import {SubtleHover} from '#/components/SubtleHover' 20 21 import {Text} from '#/components/Typography' 21 22 import type * as bsky from '#/types/bsky' 22 23 ··· 133 134 a.py_sm, 134 135 a.border, 135 136 active || hovered || pressed || focused 136 - ? [ 137 - t.atoms.bg_contrast_25, 138 - {borderColor: t.atoms.bg_contrast_25.backgroundColor}, 139 - ] 137 + ? [t.atoms.bg_contrast_25, t.atoms.border_contrast_medium] 140 138 : [t.atoms.bg, t.atoms.border_contrast_low], 141 139 ]}> 142 140 <Text ··· 186 184 {statsig: true}, 187 185 ) 188 186 }}> 189 - <View 190 - style={[ 191 - a.w_full, 192 - a.py_lg, 193 - a.px_lg, 194 - a.border_t, 195 - t.atoms.border_contrast_low, 196 - a.flex_1, 197 - ]}> 198 - <ProfileCard.Outer> 199 - <ProfileCard.Header> 200 - <ProfileCard.Avatar 201 - profile={profile} 202 - moderationOpts={moderationOpts} 203 - /> 204 - <ProfileCard.NameAndHandle 205 - profile={profile} 206 - moderationOpts={moderationOpts} 207 - /> 208 - <ProfileCard.FollowButton 209 - profile={profile} 210 - moderationOpts={moderationOpts} 211 - withIcon={false} 212 - logContext="ExploreSuggestedAccounts" 213 - onFollow={() => { 214 - logger.metric( 215 - 'suggestedUser:follow', 216 - { 217 - logContext: 'Explore', 218 - location: 'Card', 219 - recId, 220 - position, 221 - }, 222 - {statsig: true}, 223 - ) 224 - }} 225 - /> 226 - </ProfileCard.Header> 227 - <ProfileCard.Description profile={profile} numberOfLines={2} /> 228 - </ProfileCard.Outer> 229 - </View> 187 + {s => ( 188 + <> 189 + <SubtleHover hover={s.hovered || s.pressed} /> 190 + <View 191 + style={[ 192 + a.flex_1, 193 + a.w_full, 194 + a.py_lg, 195 + a.px_lg, 196 + a.border_t, 197 + t.atoms.border_contrast_low, 198 + ]}> 199 + <ProfileCard.Outer> 200 + <ProfileCard.Header> 201 + <ProfileCard.Avatar 202 + profile={profile} 203 + moderationOpts={moderationOpts} 204 + /> 205 + <ProfileCard.NameAndHandle 206 + profile={profile} 207 + moderationOpts={moderationOpts} 208 + /> 209 + <ProfileCard.FollowButton 210 + profile={profile} 211 + moderationOpts={moderationOpts} 212 + withIcon={false} 213 + logContext="ExploreSuggestedAccounts" 214 + onFollow={() => { 215 + logger.metric( 216 + 'suggestedUser:follow', 217 + { 218 + logContext: 'Explore', 219 + location: 'Card', 220 + recId, 221 + position, 222 + }, 223 + {statsig: true}, 224 + ) 225 + }} 226 + /> 227 + </ProfileCard.Header> 228 + <ProfileCard.Description profile={profile} numberOfLines={2} /> 229 + </ProfileCard.Outer> 230 + </View> 231 + </> 232 + )} 230 233 </ProfileCard.Link> 231 234 ) 232 235 }
+12 -13
src/screens/Search/modules/ExploreTrendingTopics.tsx
··· 17 17 import {Flame_Stroke2_Corner1_Rounded as FlameIcon} from '#/components/icons/Flame' 18 18 import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/icons/Trending' 19 19 import {Link} from '#/components/Link' 20 + import {SubtleHover} from '#/components/SubtleHover' 20 21 import {Text} from '#/components/Typography' 21 22 22 23 const TOPIC_COUNT = 5 ··· 28 29 } 29 30 30 31 function Inner() { 31 - const {data: trending, error, isLoading} = useGetTrendsQuery() 32 + const {data: trending, error, isLoading, isRefetching} = useGetTrendsQuery() 32 33 const noTopics = !isLoading && !error && !trending?.trends?.length 33 34 34 - return isLoading ? ( 35 + return isLoading || isRefetching ? ( 35 36 Array.from({length: TOPIC_COUNT}).map((__, i) => ( 36 37 <TrendingTopicRowSkeleton key={i} withPosts={i === 0} /> 37 38 )) ··· 92 93 PressableComponent={Pressable}> 93 94 {({hovered, pressed}) => ( 94 95 <> 95 - <View 96 - style={[ 97 - gutters, 98 - a.w_full, 99 - a.py_lg, 100 - a.flex_row, 101 - a.gap_2xs, 102 - (hovered || pressed) && t.atoms.bg_contrast_25, 103 - ]}> 96 + <SubtleHover hover={hovered || pressed} /> 97 + <View style={[gutters, a.w_full, a.py_lg, a.flex_row, a.gap_2xs]}> 104 98 <View style={[a.flex_1, a.gap_xs]}> 105 99 <View style={[a.flex_row]}> 106 100 <Text 107 - style={[a.text_md, a.font_bold, a.leading_snug, {width: 20}]}> 101 + style={[ 102 + a.text_md, 103 + a.font_bold, 104 + a.leading_tight, 105 + {width: 20}, 106 + ]}> 108 107 <Trans comment='The trending topic rank, i.e. "1. March Madness", "2. The Bachelor"'> 109 108 {rank}. 110 109 </Trans> 111 110 </Text> 112 111 <Text 113 - style={[a.text_md, a.font_bold, a.leading_snug]} 112 + style={[a.text_md, a.font_bold, a.leading_tight]} 114 113 numberOfLines={1}> 115 114 {trend.displayName} 116 115 </Text>
+53 -13
src/state/queries/explore-feed-previews.tsx
··· 34 34 const RQKEY = (feeds: string[]) => [RQKEY_ROOT, feeds] 35 35 36 36 const LIMIT = 8 // sliced to 6, overfetch to account for moderation 37 + const PINNED_POST_URIS: Record<string, boolean> = { 38 + // 📰 News 39 + 'at://did:plc:kkf4naxqmweop7dv4l2iqqf5/app.bsky.feed.post/3lgh27w2ngc2b': 40 + true, 41 + // Gardening 42 + 'at://did:plc:5rw2on4i56btlcajojaxwcat/app.bsky.feed.post/3kjorckgcwc27': 43 + true, 44 + // Web Development Trending 45 + 'at://did:plc:m2sjv3wncvsasdapla35hzwj/app.bsky.feed.post/3lfaw445axs22': 46 + true, 47 + // Anime & Manga EN 48 + 'at://did:plc:tazrmeme4dzahimsykusrwrk/app.bsky.feed.post/3knxx2gmkns2y': 49 + true, 50 + // 📽️ Film 51 + 'at://did:plc:2hwwem55ce6djnk6bn62cstr/app.bsky.feed.post/3llhpzhbq7c2g': 52 + true, 53 + // PopSky 54 + 'at://did:plc:lfdf4srj43iwdng7jn35tjsp/app.bsky.feed.post/3lbblgly65c2g': 55 + true, 56 + // Science 57 + 'at://did:plc:hu2obebw3nhfj667522dahfg/app.bsky.feed.post/3kl33otd6ob2s': 58 + true, 59 + // Birds! 🦉 60 + 'at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.post/3lbg4r57yk22d': 61 + true, 62 + // Astronomy 63 + 'at://did:plc:xy2zorw2ys47poflotxthlzg/app.bsky.feed.post/3kyzye4lujs2w': 64 + true, 65 + // What's Cooking 🍽️ 66 + 'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3lfqhgvxbqc2q': 67 + true, 68 + // BookSky 💙📚 #booksky 69 + 'at://did:plc:geoqe3qls5mwezckxxsewys2/app.bsky.feed.post/3kgrm2rw5ww2e': 70 + true, 71 + } 37 72 38 73 export type FeedPreviewItem = 39 74 | { ··· 181 216 feedContext: item.feedContext, 182 217 reason: item.reason, 183 218 feedPostUri: item.feedPostUri, 184 - items: item.items.slice(0, 6).map((subItem, i) => { 185 - const feedPostSliceItem: FeedPostSliceItem = { 186 - _reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`, 187 - uri: subItem.post.uri, 188 - post: subItem.post, 189 - record: subItem.record, 190 - moderation: moderations[i], 191 - parentAuthor: subItem.parentAuthor, 192 - isParentBlocked: subItem.isParentBlocked, 193 - isParentNotFound: subItem.isParentNotFound, 194 - } 195 - return feedPostSliceItem 196 - }), 219 + items: item.items 220 + .slice(0, 6) 221 + .filter(subItem => { 222 + return !PINNED_POST_URIS[subItem.post.uri] 223 + }) 224 + .map((subItem, i) => { 225 + const feedPostSliceItem: FeedPostSliceItem = { 226 + _reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`, 227 + uri: subItem.post.uri, 228 + post: subItem.post, 229 + record: subItem.record, 230 + moderation: moderations[i], 231 + parentAuthor: subItem.parentAuthor, 232 + isParentBlocked: subItem.isParentBlocked, 233 + isParentNotFound: subItem.isParentNotFound, 234 + } 235 + return feedPostSliceItem 236 + }), 197 237 } 198 238 if (slice.isIncompleteThread && slice.items.length >= 3) { 199 239 const beforeLast = slice.items.length - 2
+1 -2
src/state/queries/trending/useGetSuggestedFeedsQuery.ts
··· 20 20 21 21 return useQuery({ 22 22 enabled: !!preferences, 23 - refetchOnWindowFocus: true, 24 - staleTime: STALE.MINUTES.ONE, 23 + staleTime: STALE.MINUTES.THREE, 25 24 queryKey: createGetSuggestedFeedsQueryKey(), 26 25 queryFn: async () => { 27 26 const contentLangs = getContentLanguages().join(',')
+2 -2
src/state/queries/trending/useGetSuggestedUsersQuery.ts
··· 27 27 28 28 return useQuery({ 29 29 enabled: !!preferences, 30 - refetchOnWindowFocus: true, 31 - staleTime: STALE.MINUTES.ONE, 30 + staleTime: STALE.MINUTES.THREE, 32 31 queryKey: createGetSuggestedUsersQueryKey(props), 33 32 queryFn: async () => { 34 33 const contentLangs = getContentLanguages().join(',') 35 34 const {data} = await agent.app.bsky.unspecced.getSuggestedUsers( 36 35 { 37 36 category: props.category ?? undefined, 37 + limit: 10, 38 38 }, 39 39 { 40 40 headers: {
-1
src/state/queries/trending/useGetTrendsQuery.ts
··· 25 25 26 26 return useQuery({ 27 27 enabled: !!preferences, 28 - refetchOnWindowFocus: true, 29 28 staleTime: STALE.MINUTES.THREE, 30 29 queryKey: createGetTrendsQueryKey(), 31 30 queryFn: async () => {
+1 -2
src/state/queries/useSuggestedStarterPacksQuery.ts
··· 20 20 21 21 return useQuery({ 22 22 enabled: !!preferences, 23 - refetchOnWindowFocus: true, 24 - staleTime: STALE.MINUTES.ONE, 23 + staleTime: STALE.MINUTES.THREE, 25 24 queryKey: createSuggestedStarterPacksQueryKey(), 26 25 async queryFn() { 27 26 const {data} = await agent.app.bsky.unspecced.getSuggestedStarterPacks(