diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index c8a55b92..6a408118 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -2,11 +2,12 @@ export type Gate = // Keep this alphabetic please. | 'debug_show_feedcontext' | 'native_pwi_disabled' + | 'new_user_guided_tour' + | 'new_user_progress_guide' | 'onboarding_minimum_interests' | 'request_notifications_permission_after_onboarding_v2' | 'show_avi_follow_button' | 'show_follow_back_label_v2' - | 'new_user_guided_tour' - | 'new_user_progress_guide' | 'suggested_feeds_interstitial' | 'suggested_follows_interstitial' + | 'ungroup_follow_backs' diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index 13ca3ffd..17ee9092 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -26,6 +26,7 @@ import { useQueryClient, } from '@tanstack/react-query' +import {useGate} from '#/lib/statsig/statsig' import {useAgent} from '#/state/session' import {useModerationOpts} from '../../preferences/moderation-opts' import {STALE} from '..' @@ -56,6 +57,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { const unreads = useUnreadNotificationsApi() const enabled = opts?.enabled !== false const lastPageCountRef = useRef(0) + const gate = useGate() const query = useInfiniteQuery< FeedPage, @@ -81,6 +83,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { queryClient, moderationOpts, fetchAdditionalData: true, + shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'), }) ).page } diff --git a/src/state/queries/notifications/unread.tsx b/src/state/queries/notifications/unread.tsx index 7bb325ea..b5f7d0d6 100644 --- a/src/state/queries/notifications/unread.tsx +++ b/src/state/queries/notifications/unread.tsx @@ -8,6 +8,7 @@ import {useQueryClient} from '@tanstack/react-query' import EventEmitter from 'eventemitter3' import BroadcastChannel from '#/lib/broadcast' +import {useGate} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {useAgent, useSession} from '#/state/session' import {resetBadgeCount} from 'lib/notifications/notifications' @@ -47,6 +48,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const agent = useAgent() const queryClient = useQueryClient() const moderationOpts = useModerationOpts() + const gate = useGate() const [numUnread, setNumUnread] = React.useState('') @@ -149,6 +151,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { // only fetch subjects when the page is going to be used // in the notifications query, otherwise skip it fetchAdditionalData: !!invalidate, + shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'), }) const unreadCount = countUnread(page) const unreadCountStr = @@ -189,7 +192,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { } }, } - }, [setNumUnread, queryClient, moderationOpts, agent]) + }, [setNumUnread, queryClient, moderationOpts, agent, gate]) checkUnreadRef.current = api.checkUnread return ( diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index ade98b31..2f2c242d 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -30,6 +30,7 @@ export async function fetchPage({ queryClient, moderationOpts, fetchAdditionalData, + shouldUngroupFollowBacks, }: { agent: BskyAgent cursor: string | undefined @@ -37,6 +38,7 @@ export async function fetchPage({ queryClient: QueryClient moderationOpts: ModerationOpts | undefined fetchAdditionalData: boolean + shouldUngroupFollowBacks?: () => boolean }): Promise<{page: FeedPage; indexedAt: string | undefined}> { const res = await agent.listNotifications({ limit, @@ -51,7 +53,7 @@ export async function fetchPage({ ) // group notifications which are essentially similar (follows, likes on a post) - let notifsGrouped = groupNotifications(notifs) + let notifsGrouped = groupNotifications(notifs, {shouldUngroupFollowBacks}) // we fetch subjects of notifications (usually posts) now instead of lazily // in the UI to avoid relayouts @@ -109,6 +111,7 @@ export function shouldFilterNotif( export function groupNotifications( notifs: AppBskyNotificationListNotifications.Notification[], + options?: {shouldUngroupFollowBacks?: () => boolean}, ): FeedNotification[] { const groupedNotifs: FeedNotification[] = [] for (const notif of notifs) { @@ -123,10 +126,20 @@ export function groupNotifications( notif.reasonSubject === groupedNotif.notification.reasonSubject && notif.author.did !== groupedNotif.notification.author.did ) { - groupedNotif.additional = groupedNotif.additional || [] - groupedNotif.additional.push(notif) - grouped = true - break + const nextIsFollowBack = + notif.reason === 'follow' && notif.author.viewer?.following + const prevIsFollowBack = + groupedNotif.notification.reason === 'follow' && + groupedNotif.notification.author.viewer?.following + const shouldUngroup = + (nextIsFollowBack || prevIsFollowBack) && + options?.shouldUngroupFollowBacks?.() + if (!shouldUngroup) { + groupedNotif.additional = groupedNotif.additional || [] + groupedNotif.additional.push(notif) + grouped = true + break + } } } } diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 4f84385d..1932efbd 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -22,6 +22,7 @@ import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' +import {useGate} from '#/lib/statsig/statsig' import {FeedNotification} from '#/state/queries/notifications/feed' import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' import {usePalette} from 'lib/hooks/usePalette' @@ -86,6 +87,7 @@ let FeedItem = ({ const pal = usePalette('default') const {_} = useLingui() const t = useTheme() + const gate = useGate() const [isAuthorsExpanded, setAuthorsExpanded] = useState(false) const itemHref = useMemo(() => { if (item.type === 'post-like' || item.type === 'repost') { @@ -168,6 +170,7 @@ let FeedItem = ({ ) } + let isFollowBack = false let action = '' let icon = ( } else if (item.type === 'follow') { - action = _(msg`followed you`) + if ( + item.notification.author.viewer?.following && + gate('ungroup_follow_backs') + ) { + isFollowBack = true + action = _(msg`followed you back`) + } else { + action = _(msg`followed you`) + } icon = } else if (item.type === 'feedgen-like') { action = _(msg`liked your custom feed`) @@ -260,7 +271,7 @@ let FeedItem = ({ visible={!isAuthorsExpanded} authors={authors} onToggleAuthorsExpanded={onToggleAuthorsExpanded} - showDmButton={item.type === 'starterpack-joined'} + showDmButton={item.type === 'starterpack-joined' || isFollowBack} />