From 92ec563f9dba16fd040e791170458f3f93f8a31f Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Tue, 21 Jul 2026 05:16:22 -0700 Subject: [PATCH] Restore activity notification subscriptions screen (#11201) --- bskyweb/cmd/bskyweb/server.go | 1 + src/Navigation.tsx | 9 + src/lib/routes/types.ts | 1 + src/routes.ts | 1 + .../ActivityNotificationSettings.tsx | 264 ++++++++++++++++++ .../Settings/NotificationSettings/index.tsx | 20 +- 6 files changed, 279 insertions(+), 17 deletions(-) create mode 100644 src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 7373dc5cdc..eb59adf40f 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -328,6 +328,7 @@ func serve(cctx *cli.Context) error { e.GET("/settings/interests", server.WebGenericNoindex) e.GET("/settings/about", server.WebGenericNoindex) e.GET("/settings/notifications", server.WebGenericNoindex) + e.GET("/settings/notifications/activity", server.WebGenericNoindex) e.GET("/sys/debug", server.WebGenericNoindex) e.GET("/sys/debug-mod", server.WebGenericNoindex) e.GET("/sys/log", server.WebGenericNoindex) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 5207a3a123..ab596876aa 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -117,6 +117,7 @@ import {InterestsSettingsScreen} from '#/screens/Settings/InterestsSettings' import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings' import {LegacyNotificationSettingsScreen} from '#/screens/Settings/LegacyNotificationSettings' import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings' +import {ActivityNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/ActivityNotificationSettings' import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings' import {SettingsScreen} from '#/screens/Settings/Settings' import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences' @@ -451,6 +452,14 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { getComponent={() => NotificationSettingsScreen} options={{title: title(msg`Notification settings`), requireAuth: true}} /> + ActivityNotificationSettingsScreen} + options={{ + title: title(msg`Activity notifications`), + requireAuth: true, + }} + /> ContentAndMediaSettingsScreen} diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 6eb112a381..9108b01e07 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -70,6 +70,7 @@ export type CommonNavigatorParams = { ActivityPrivacySettings: undefined ContentAndMediaSettings: undefined NotificationSettings: undefined + ActivityNotificationSettings: undefined InterestsSettings: undefined AboutSettings: undefined AppIconSettings: undefined diff --git a/src/routes.ts b/src/routes.ts index 3e45f1b379..c996dfde9b 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -61,6 +61,7 @@ export const router = new Router({ AboutSettings: '/settings/about', AppIconSettings: '/settings/app-icon', NotificationSettings: '/settings/notifications', + ActivityNotificationSettings: '/settings/notifications/activity', FindContactsSettings: '/settings/find-contacts', // support Support: '/support', diff --git a/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx new file mode 100644 index 0000000000..168593ebe8 --- /dev/null +++ b/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx @@ -0,0 +1,264 @@ +import {useCallback, useMemo} from 'react' +import {type ListRenderItemInfo, Text as RNText, View} from 'react-native' +import {type ModerationOpts} from '@atproto/api' +import {useLingui} from '@lingui/react/macro' +import {Trans} from '@lingui/react/macro' + +import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' +import { + type AllNavigatorParams, + type NativeStackScreenProps, +} from '#/lib/routes/types' +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useActivitySubscriptionsQuery} from '#/state/queries/activity-subscriptions' +import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' +import {List} from '#/view/com/util/List' +import {atoms as a, useTheme} from '#/alf' +import {SubscribeProfileDialog} from '#/components/activity-notifications/SubscribeProfileDialog' +import * as Admonition from '#/components/Admonition' +import {Button, ButtonText} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import { + BellRinging_Filled_Corner0_Rounded as BellRingingFilledIcon, + BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon, +} from '#/components/icons/BellRinging' +import * as Layout from '#/components/Layout' +import {InlineLinkText} from '#/components/Link' +import {ListFooter} from '#/components/Lists' +import {Loader} from '#/components/Loader' +import * as ProfileCard from '#/components/ProfileCard' +import {Text} from '#/components/Typography' +import type * as bsky from '#/types/bsky' +import * as SettingsList from '../components/SettingsList' +import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle' +import {PreferenceControls} from './components/PreferenceControls' + +type Props = NativeStackScreenProps< + AllNavigatorParams, + 'ActivityNotificationSettings' +> + +export function ActivityNotificationSettingsScreen({}: Props) { + const t = useTheme() + const {t: l} = useLingui() + const {data: preferences, isError: isPreferencesError} = + useNotificationSettingsQuery() + const moderationOpts = useModerationOpts() + + const { + data: subscriptions, + isPending, + isError: isSubscriptionsError, + error, + isFetchingNextPage, + fetchNextPage, + hasNextPage, + } = useActivitySubscriptionsQuery() + + const items = useMemo(() => { + if (!subscriptions) return [] + return subscriptions.pages.flatMap(page => page.subscriptions) + }, [subscriptions]) + + const renderItem = useCallback( + ({item}: ListRenderItemInfo) => { + if (!moderationOpts) return null + return ( + + ) + }, + [moderationOpts], + ) + + const onEndReached = useCallback(() => { + if (isFetchingNextPage || !hasNextPage || isSubscriptionsError) return + void fetchNextPage().catch(err => { + logger.error('Failed to load more activity subscriptions', { + message: err, + }) + }) + }, [isFetchingNextPage, hasNextPage, isSubscriptionsError, fetchNextPage]) + + return ( + + + + + + Notifications + + + + + + + + + + {isPreferencesError ? ( + + + Failed to load notification settings. + + + ) : ( + + + + )} + + } + data={items} + keyExtractor={keyExtractor} + renderItem={renderItem} + onEndReached={onEndReached} + onEndReachedThreshold={4} + ListEmptyComponent={ + error ? null : ( + + {!isPending ? ( + + + + + + + Enable notifications for an account by visiting their + profile and pressing the{' '} + + bell icon + {' '} + + . + + + + + If you want to restrict who can receive notifications + for your account's activity, you can change this in{' '} + + Settings → Privacy and Security + + . + + + + + + ) : ( + + + + )} + + ) + } + ListFooterComponent={ + + } + windowSize={11} + /> + + ) +} + +function keyExtractor(item: bsky.profile.AnyProfileView) { + return item.did +} + +function ActivitySubscriptionCard({ + profile: profileUnshadowed, + moderationOpts, +}: { + profile: bsky.profile.AnyProfileView + moderationOpts: ModerationOpts +}) { + const profile = useProfileShadow(profileUnshadowed) + const control = useDialogControl() + const {t: l} = useLingui() + const t = useTheme() + + const preview = useMemo(() => { + const actSub = profile.viewer?.activitySubscription + if (actSub?.post && actSub?.reply) { + return l`Posts, Replies` + } else if (actSub?.post) { + return l`Posts` + } else if (actSub?.reply) { + return l`Replies` + } + return l`None` + }, [l, profile.viewer?.activitySubscription]) + + return ( + + + + + + + + {preview} + + + + + + + + ) +} diff --git a/src/screens/Settings/NotificationSettings/index.tsx b/src/screens/Settings/NotificationSettings/index.tsx index 00318a9180..16671508b2 100644 --- a/src/screens/Settings/NotificationSettings/index.tsx +++ b/src/screens/Settings/NotificationSettings/index.tsx @@ -57,7 +57,6 @@ export function NotificationSettingsScreen({}: Props) { const mentionDialogControl = Dialog.useDialogControl() const quoteDialogControl = Dialog.useDialogControl() const repostDialogControl = Dialog.useDialogControl() - const activityDialogControl = Dialog.useDialogControl() const likeRepostDialogControl = Dialog.useDialogControl() const repostRepostDialogControl = Dialog.useDialogControl() const chatDialogControl = Dialog.useDialogControl() @@ -217,9 +216,9 @@ export function NotificationSettingsScreen({}: Props) { showSkeleton={!settings} /> - - + Get notifications when people repost your posts. } /> - Activity from others} - subtitleText={ - - Get notifications when there's activity on posts you're subscribed - to. - - } - allowDisableInApp={false} - />