diff options
-rw-r--r-- | src/components/FeedInterstitials.tsx | 60 | ||||
-rw-r--r-- | src/lib/statsig/gates.ts | 1 | ||||
-rw-r--r-- | src/screens/Profile/Header/ProfileHeaderStandard.tsx | 44 | ||||
-rw-r--r-- | src/view/com/posts/Feed.tsx | 43 |
4 files changed, 110 insertions, 38 deletions
diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index e37d2c3e0..65e981f77 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -1,18 +1,21 @@ import React from 'react' import {View} from 'react-native' import {ScrollView} from 'react-native-gesture-handler' -import {AppBskyFeedDefs, AtUri} from '@atproto/api' +import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {NavigationProp} from '#/lib/routes/types' +import {useGate} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useGetPopularFeedsQuery} from '#/state/queries/feed' +import {FeedDescriptor} from '#/state/queries/post-feed' import {useProfilesQuery} from '#/state/queries/profile' +import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' import {useSession} from '#/state/session' import {useProgressGuide} from '#/state/shell/progress-guide' import * as userActionHistory from '#/state/userActionHistory' @@ -173,14 +176,63 @@ function useExperimentalSuggestedUsersQuery() { } } -export function SuggestedFollows() { - const t = useTheme() - const {_} = useLingui() +export function SuggestedFollows({feed}: {feed: FeedDescriptor}) { + const gate = useGate() + const [feedType, feedUri] = feed.split('|') + if (feedType === 'author') { + if (gate('show_follow_suggestions_in_profile')) { + return <SuggestedFollowsProfile did={feedUri} /> + } else { + return null + } + } else { + return <SuggestedFollowsHome /> + } +} + +export function SuggestedFollowsProfile({did}: {did: string}) { + const { + isLoading: isSuggestionsLoading, + data, + error, + } = useSuggestedFollowsByActorQuery({ + did, + }) + return ( + <ProfileGrid + isSuggestionsLoading={isSuggestionsLoading} + profiles={data?.suggestions ?? []} + error={error} + /> + ) +} + +export function SuggestedFollowsHome() { const { isLoading: isSuggestionsLoading, profiles, error, } = useExperimentalSuggestedUsersQuery() + return ( + <ProfileGrid + isSuggestionsLoading={isSuggestionsLoading} + profiles={profiles} + error={error} + /> + ) +} + +export function ProfileGrid({ + isSuggestionsLoading, + error, + profiles, +}: { + isSuggestionsLoading: boolean + profiles: AppBskyActorDefs.ProfileViewDetailed[] + error: Error | null +}) { + const t = useTheme() + const {_} = useLingui() const moderationOpts = useModerationOpts() const navigation = useNavigation<NavigationProp>() const {gtMobile} = useBreakpoints() diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index d4478477b..f45be0b79 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -4,5 +4,6 @@ export type Gate = | 'fixed_bottom_bar' | 'onboarding_minimum_interests' | 'suggested_feeds_interstitial' + | 'show_follow_suggestions_in_profile' | 'video_debug' | 'videos' diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 2b6353b27..2036023c3 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -10,6 +10,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useGate} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {isIOS} from '#/platform/detection' import {Shadow} from '#/state/cache/types' @@ -59,6 +60,7 @@ let ProfileHeaderStandard = ({ const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> = useProfileShadow(profileUnshadowed) const t = useTheme() + const gate = useGate() const {currentAccount, hasSession} = useSession() const {_} = useLingui() const {openModal} = useModalControls() @@ -203,27 +205,29 @@ let ProfileHeaderStandard = ({ {hasSession && ( <> <MessageProfileButton profile={profile} /> - <Button - testID="suggestedFollowsBtn" - size="small" - color={showSuggestedFollows ? 'primary' : 'secondary'} - variant="solid" - shape="round" - onPress={() => - setShowSuggestedFollows(!showSuggestedFollows) - } - label={_(msg`Show follows similar to ${profile.handle}`)} - style={{width: 36, height: 36}}> - <FontAwesomeIcon - icon="user-plus" - style={ - showSuggestedFollows - ? {color: t.palette.white} - : t.atoms.text + {!gate('show_follow_suggestions_in_profile') && ( + <Button + testID="suggestedFollowsBtn" + size="small" + color={showSuggestedFollows ? 'primary' : 'secondary'} + variant="solid" + shape="round" + onPress={() => + setShowSuggestedFollows(!showSuggestedFollows) } - size={14} - /> - </Button> + label={_(msg`Show follows similar to ${profile.handle}`)} + style={{width: 36, height: 36}}> + <FontAwesomeIcon + icon="user-plus" + style={ + showSuggestedFollows + ? {color: t.palette.white} + : t.atoms.text + } + size={14} + /> + </Button> + )} </> )} diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index f0709a3ea..bad6ccfea 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -101,7 +101,7 @@ const feedInterstitialType = 'interstitialFeeds' const followInterstitialType = 'interstitialFollows' const progressGuideInterstitialType = 'interstitialProgressGuide' const interstials: Record< - 'following' | 'discover', + 'following' | 'discover' | 'profile', (FeedItem & { type: | 'interstitialFeeds' @@ -128,6 +128,16 @@ const interstials: Record< slot: 20, }, ], + profile: [ + { + type: followInterstitialType, + params: { + variant: 'default', + }, + key: followInterstitialType, + slot: 5, + }, + ], } export function getFeedPostSlice(feedItem: FeedItem): FeedPostSlice | null { @@ -193,9 +203,7 @@ let Feed = ({ const [isPTRing, setIsPTRing] = React.useState(false) const checkForNewRef = React.useRef<(() => void) | null>(null) const lastFetchRef = React.useRef<number>(Date.now()) - const [feedType, feedUri] = feed.split('|') - const feedIsDiscover = feedUri === DISCOVER_FEED_URI - const feedIsFollowing = feedType === 'following' + const [feedType, feedUri, feedTab] = feed.split('|') const gate = useGate() const opts = React.useMemo( @@ -339,14 +347,21 @@ let Feed = ({ } if (hasSession) { - const feedType = feedIsFollowing - ? 'following' - : feedIsDiscover - ? 'discover' - : undefined + let feedKind: 'following' | 'discover' | 'profile' | undefined + if (feedType === 'following') { + feedKind = 'following' + } else if (feedUri === DISCOVER_FEED_URI) { + feedKind = 'discover' + } else if ( + feedType === 'author' && + (feedTab === 'posts_and_author_threads' || + feedTab === 'posts_with_replies') + ) { + feedKind = 'profile' + } - if (feedType) { - for (const interstitial of interstials[feedType]) { + if (feedKind) { + for (const interstitial of interstials[feedKind]) { const shouldShow = (interstitial.type === feedInterstitialType && gate('suggested_feeds_interstitial')) || @@ -377,9 +392,9 @@ let Feed = ({ isEmpty, lastFetchedAt, data, + feedType, feedUri, - feedIsDiscover, - feedIsFollowing, + feedTab, gate, hasSession, ]) @@ -470,7 +485,7 @@ let Feed = ({ } else if (item.type === feedInterstitialType) { return <SuggestedFeeds /> } else if (item.type === followInterstitialType) { - return <SuggestedFollows /> + return <SuggestedFollows feed={feed} /> } else if (item.type === progressGuideInterstitialType) { return <ProgressGuide /> } else if (item.type === 'slice') { |