Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

[Experiment] Suggest profiles in profile (#5030)

* Rename variable to disambiguate with parent scope

* More variables where they are used

* Inline variables

* Add suggestions in profile

* Gate it

* rm space

* Remove header suggestions under gate

authored by

dan and committed by
GitHub
dbbbba1d 46b7193a

+110 -38
+56 -4
src/components/FeedInterstitials.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 import {ScrollView} from 'react-native-gesture-handler' 4 - import {AppBskyFeedDefs, AtUri} from '@atproto/api' 4 + import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' 5 5 import {msg, Trans} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 7 import {useNavigation} from '@react-navigation/native' 8 8 9 9 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 10 10 import {NavigationProp} from '#/lib/routes/types' 11 + import {useGate} from '#/lib/statsig/statsig' 11 12 import {logEvent} from '#/lib/statsig/statsig' 12 13 import {logger} from '#/logger' 13 14 import {useModerationOpts} from '#/state/preferences/moderation-opts' 14 15 import {useGetPopularFeedsQuery} from '#/state/queries/feed' 16 + import {FeedDescriptor} from '#/state/queries/post-feed' 15 17 import {useProfilesQuery} from '#/state/queries/profile' 18 + import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' 16 19 import {useSession} from '#/state/session' 17 20 import {useProgressGuide} from '#/state/shell/progress-guide' 18 21 import * as userActionHistory from '#/state/userActionHistory' ··· 173 176 } 174 177 } 175 178 176 - export function SuggestedFollows() { 177 - const t = useTheme() 178 - const {_} = useLingui() 179 + export function SuggestedFollows({feed}: {feed: FeedDescriptor}) { 180 + const gate = useGate() 181 + const [feedType, feedUri] = feed.split('|') 182 + if (feedType === 'author') { 183 + if (gate('show_follow_suggestions_in_profile')) { 184 + return <SuggestedFollowsProfile did={feedUri} /> 185 + } else { 186 + return null 187 + } 188 + } else { 189 + return <SuggestedFollowsHome /> 190 + } 191 + } 192 + 193 + export function SuggestedFollowsProfile({did}: {did: string}) { 194 + const { 195 + isLoading: isSuggestionsLoading, 196 + data, 197 + error, 198 + } = useSuggestedFollowsByActorQuery({ 199 + did, 200 + }) 201 + return ( 202 + <ProfileGrid 203 + isSuggestionsLoading={isSuggestionsLoading} 204 + profiles={data?.suggestions ?? []} 205 + error={error} 206 + /> 207 + ) 208 + } 209 + 210 + export function SuggestedFollowsHome() { 179 211 const { 180 212 isLoading: isSuggestionsLoading, 181 213 profiles, 182 214 error, 183 215 } = useExperimentalSuggestedUsersQuery() 216 + return ( 217 + <ProfileGrid 218 + isSuggestionsLoading={isSuggestionsLoading} 219 + profiles={profiles} 220 + error={error} 221 + /> 222 + ) 223 + } 224 + 225 + export function ProfileGrid({ 226 + isSuggestionsLoading, 227 + error, 228 + profiles, 229 + }: { 230 + isSuggestionsLoading: boolean 231 + profiles: AppBskyActorDefs.ProfileViewDetailed[] 232 + error: Error | null 233 + }) { 234 + const t = useTheme() 235 + const {_} = useLingui() 184 236 const moderationOpts = useModerationOpts() 185 237 const navigation = useNavigation<NavigationProp>() 186 238 const {gtMobile} = useBreakpoints()
+1
src/lib/statsig/gates.ts
··· 4 4 | 'fixed_bottom_bar' 5 5 | 'onboarding_minimum_interests' 6 6 | 'suggested_feeds_interstitial' 7 + | 'show_follow_suggestions_in_profile' 7 8 | 'video_debug' 8 9 | 'videos'
+24 -20
src/screens/Profile/Header/ProfileHeaderStandard.tsx
··· 10 10 import {msg, Trans} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 12 13 + import {useGate} from '#/lib/statsig/statsig' 13 14 import {logger} from '#/logger' 14 15 import {isIOS} from '#/platform/detection' 15 16 import {Shadow} from '#/state/cache/types' ··· 59 60 const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> = 60 61 useProfileShadow(profileUnshadowed) 61 62 const t = useTheme() 63 + const gate = useGate() 62 64 const {currentAccount, hasSession} = useSession() 63 65 const {_} = useLingui() 64 66 const {openModal} = useModalControls() ··· 203 205 {hasSession && ( 204 206 <> 205 207 <MessageProfileButton profile={profile} /> 206 - <Button 207 - testID="suggestedFollowsBtn" 208 - size="small" 209 - color={showSuggestedFollows ? 'primary' : 'secondary'} 210 - variant="solid" 211 - shape="round" 212 - onPress={() => 213 - setShowSuggestedFollows(!showSuggestedFollows) 214 - } 215 - label={_(msg`Show follows similar to ${profile.handle}`)} 216 - style={{width: 36, height: 36}}> 217 - <FontAwesomeIcon 218 - icon="user-plus" 219 - style={ 220 - showSuggestedFollows 221 - ? {color: t.palette.white} 222 - : t.atoms.text 208 + {!gate('show_follow_suggestions_in_profile') && ( 209 + <Button 210 + testID="suggestedFollowsBtn" 211 + size="small" 212 + color={showSuggestedFollows ? 'primary' : 'secondary'} 213 + variant="solid" 214 + shape="round" 215 + onPress={() => 216 + setShowSuggestedFollows(!showSuggestedFollows) 223 217 } 224 - size={14} 225 - /> 226 - </Button> 218 + label={_(msg`Show follows similar to ${profile.handle}`)} 219 + style={{width: 36, height: 36}}> 220 + <FontAwesomeIcon 221 + icon="user-plus" 222 + style={ 223 + showSuggestedFollows 224 + ? {color: t.palette.white} 225 + : t.atoms.text 226 + } 227 + size={14} 228 + /> 229 + </Button> 230 + )} 227 231 </> 228 232 )} 229 233
+29 -14
src/view/com/posts/Feed.tsx
··· 101 101 const followInterstitialType = 'interstitialFollows' 102 102 const progressGuideInterstitialType = 'interstitialProgressGuide' 103 103 const interstials: Record< 104 - 'following' | 'discover', 104 + 'following' | 'discover' | 'profile', 105 105 (FeedItem & { 106 106 type: 107 107 | 'interstitialFeeds' ··· 126 126 }, 127 127 key: followInterstitialType, 128 128 slot: 20, 129 + }, 130 + ], 131 + profile: [ 132 + { 133 + type: followInterstitialType, 134 + params: { 135 + variant: 'default', 136 + }, 137 + key: followInterstitialType, 138 + slot: 5, 129 139 }, 130 140 ], 131 141 } ··· 193 203 const [isPTRing, setIsPTRing] = React.useState(false) 194 204 const checkForNewRef = React.useRef<(() => void) | null>(null) 195 205 const lastFetchRef = React.useRef<number>(Date.now()) 196 - const [feedType, feedUri] = feed.split('|') 197 - const feedIsDiscover = feedUri === DISCOVER_FEED_URI 198 - const feedIsFollowing = feedType === 'following' 206 + const [feedType, feedUri, feedTab] = feed.split('|') 199 207 const gate = useGate() 200 208 201 209 const opts = React.useMemo( ··· 339 347 } 340 348 341 349 if (hasSession) { 342 - const feedType = feedIsFollowing 343 - ? 'following' 344 - : feedIsDiscover 345 - ? 'discover' 346 - : undefined 350 + let feedKind: 'following' | 'discover' | 'profile' | undefined 351 + if (feedType === 'following') { 352 + feedKind = 'following' 353 + } else if (feedUri === DISCOVER_FEED_URI) { 354 + feedKind = 'discover' 355 + } else if ( 356 + feedType === 'author' && 357 + (feedTab === 'posts_and_author_threads' || 358 + feedTab === 'posts_with_replies') 359 + ) { 360 + feedKind = 'profile' 361 + } 347 362 348 - if (feedType) { 349 - for (const interstitial of interstials[feedType]) { 363 + if (feedKind) { 364 + for (const interstitial of interstials[feedKind]) { 350 365 const shouldShow = 351 366 (interstitial.type === feedInterstitialType && 352 367 gate('suggested_feeds_interstitial')) || ··· 377 392 isEmpty, 378 393 lastFetchedAt, 379 394 data, 395 + feedType, 380 396 feedUri, 381 - feedIsDiscover, 382 - feedIsFollowing, 397 + feedTab, 383 398 gate, 384 399 hasSession, 385 400 ]) ··· 470 485 } else if (item.type === feedInterstitialType) { 471 486 return <SuggestedFeeds /> 472 487 } else if (item.type === followInterstitialType) { 473 - return <SuggestedFollows /> 488 + return <SuggestedFollows feed={feed} /> 474 489 } else if (item.type === progressGuideInterstitialType) { 475 490 return <ProgressGuide /> 476 491 } else if (item.type === 'slice') {