about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Navigation.tsx99
-rw-r--r--src/components/icons/BellRinging.tsx5
-rw-r--r--src/components/icons/Heart2.tsx4
-rw-r--r--src/components/icons/Phone.tsx4
-rw-r--r--src/components/icons/Repost.tsx4
-rw-r--r--src/lib/routes/router.ts6
-rw-r--r--src/lib/routes/types.ts24
-rw-r--r--src/routes.ts26
-rw-r--r--src/screens/Messages/Settings.tsx4
-rw-r--r--src/screens/Settings/InterestsSettings.tsx (renamed from src/screens/Settings/SettingsInterests.tsx)5
-rw-r--r--src/screens/Settings/LegacyNotificationSettings.tsx21
-rw-r--r--src/screens/Settings/NotificationSettings.tsx98
-rw-r--r--src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx60
-rw-r--r--src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx65
-rw-r--r--src/screens/Settings/NotificationSettings/MentionNotificationSettings.tsx63
-rw-r--r--src/screens/Settings/NotificationSettings/MiscellaneousNotificationSettings.tsx68
-rw-r--r--src/screens/Settings/NotificationSettings/NewFollowerNotificationSettings.tsx63
-rw-r--r--src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx60
-rw-r--r--src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx66
-rw-r--r--src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx63
-rw-r--r--src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx66
-rw-r--r--src/screens/Settings/NotificationSettings/components/ItemTextWithSubtitle.tsx34
-rw-r--r--src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx194
-rw-r--r--src/screens/Settings/NotificationSettings/index.tsx293
-rw-r--r--src/screens/Settings/Settings.tsx9
-rw-r--r--src/state/queries/notifications/settings.ts99
-rw-r--r--src/view/screens/Notifications.tsx2
27 files changed, 1325 insertions, 180 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index 2f26c0971..3bf1ace85 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -90,11 +90,10 @@ import {AppPasswordsScreen} from '#/screens/Settings/AppPasswords'
 import {ContentAndMediaSettingsScreen} from '#/screens/Settings/ContentAndMediaSettings'
 import {ExternalMediaPreferencesScreen} from '#/screens/Settings/ExternalMediaPreferences'
 import {FollowingFeedPreferencesScreen} from '#/screens/Settings/FollowingFeedPreferences'
+import {InterestsSettingsScreen} from '#/screens/Settings/InterestsSettings'
 import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings'
-import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
 import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings'
 import {SettingsScreen} from '#/screens/Settings/Settings'
-import {SettingsInterests} from '#/screens/Settings/SettingsInterests'
 import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences'
 import {
   StarterPackScreen,
@@ -110,6 +109,17 @@ import {
 } from '#/components/dialogs/EmailDialog'
 import {router} from '#/routes'
 import {Referrer} from '../modules/expo-bluesky-swiss-army'
+import {LegacyNotificationSettingsScreen} from './screens/Settings/LegacyNotificationSettings'
+import {NotificationSettingsScreen} from './screens/Settings/NotificationSettings'
+import {LikeNotificationSettingsScreen} from './screens/Settings/NotificationSettings/LikeNotificationSettings'
+import {LikesOnRepostsNotificationSettingsScreen} from './screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings'
+import {MentionNotificationSettingsScreen} from './screens/Settings/NotificationSettings/MentionNotificationSettings'
+import {MiscellaneousNotificationSettingsScreen} from './screens/Settings/NotificationSettings/MiscellaneousNotificationSettings'
+import {NewFollowerNotificationSettingsScreen} from './screens/Settings/NotificationSettings/NewFollowerNotificationSettings'
+import {QuoteNotificationSettingsScreen} from './screens/Settings/NotificationSettings/QuoteNotificationSettings'
+import {ReplyNotificationSettingsScreen} from './screens/Settings/NotificationSettings/ReplyNotificationSettings'
+import {RepostNotificationSettingsScreen} from './screens/Settings/NotificationSettings/RepostNotificationSettings'
+import {RepostsOnRepostsNotificationSettingsScreen} from './screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings'
 
 const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
 
@@ -381,6 +391,83 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
         }}
       />
       <Stack.Screen
+        name="NotificationSettings"
+        getComponent={() => NotificationSettingsScreen}
+        options={{title: title(msg`Notification settings`), requireAuth: true}}
+      />
+      <Stack.Screen
+        name="ReplyNotificationSettings"
+        getComponent={() => ReplyNotificationSettingsScreen}
+        options={{
+          title: title(msg`Reply notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
+        name="MentionNotificationSettings"
+        getComponent={() => MentionNotificationSettingsScreen}
+        options={{
+          title: title(msg`Mention notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
+        name="QuoteNotificationSettings"
+        getComponent={() => QuoteNotificationSettingsScreen}
+        options={{
+          title: title(msg`Quote notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
+        name="LikeNotificationSettings"
+        getComponent={() => LikeNotificationSettingsScreen}
+        options={{
+          title: title(msg`Like notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
+        name="RepostNotificationSettings"
+        getComponent={() => RepostNotificationSettingsScreen}
+        options={{
+          title: title(msg`Repost notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
+        name="NewFollowerNotificationSettings"
+        getComponent={() => NewFollowerNotificationSettingsScreen}
+        options={{
+          title: title(msg`New follower notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
+        name="LikesOnRepostsNotificationSettings"
+        getComponent={() => LikesOnRepostsNotificationSettingsScreen}
+        options={{
+          title: title(msg`Likes on your reposts notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
+        name="RepostsOnRepostsNotificationSettings"
+        getComponent={() => RepostsOnRepostsNotificationSettingsScreen}
+        options={{
+          title: title(msg`Reposts on your reposts notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
+        name="MiscellaneousNotificationSettings"
+        getComponent={() => MiscellaneousNotificationSettingsScreen}
+        options={{
+          title: title(msg`Miscellaneous notifications`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
         name="ContentAndMediaSettings"
         getComponent={() => ContentAndMediaSettingsScreen}
         options={{
@@ -389,8 +476,8 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
         }}
       />
       <Stack.Screen
-        name="SettingsInterests"
-        getComponent={() => SettingsInterests}
+        name="InterestsSettings"
+        getComponent={() => InterestsSettingsScreen}
         options={{
           title: title(msg`Your interests`),
           requireAuth: true,
@@ -438,8 +525,8 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
         options={{title: title(msg`Chat request inbox`), requireAuth: true}}
       />
       <Stack.Screen
-        name="NotificationSettings"
-        getComponent={() => NotificationSettingsScreen}
+        name="LegacyNotificationSettings"
+        getComponent={() => LegacyNotificationSettingsScreen}
         options={{title: title(msg`Notification settings`), requireAuth: true}}
       />
       <Stack.Screen
diff --git a/src/components/icons/BellRinging.tsx b/src/components/icons/BellRinging.tsx
new file mode 100644
index 000000000..b174fcedc
--- /dev/null
+++ b/src/components/icons/BellRinging.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const BellRinging_Stroke2_Corner0_Rounded = createSinglePathSVG({
+  path: 'M12 2a7.854 7.854 0 0 1 7.785 6.815l1.055 7.92.018.224a2 2 0 0 1-2 2.041h-2.215c-.904 1.747-2.605 3-4.643 3s-3.739-1.253-4.643-3H5.142a2 2 0 0 1-1.982-2.265l1.056-7.92.057-.363A7.854 7.854 0 0 1 12 2ZM9.78 19c.609.637 1.398 1 2.22 1s1.611-.363 2.22-1H9.78ZM12 4a5.854 5.854 0 0 0-5.76 4.81l-.041.27L5.142 17h13.716l-1.056-7.92A5.854 5.854 0 0 0 12 4ZM2.718 7.464a1 1 0 1 1-1.953-.427l1.953.427Zm20.518-.427a1 1 0 0 1-1.954.427l1.954-.427ZM3.193 2.105a1 1 0 0 1 1.531 1.287 9.47 9.47 0 0 0-2.006 4.072L.765 7.037a11.46 11.46 0 0 1 2.428-4.932Zm16.205-.123a1 1 0 0 1 1.34.047l.069.076.217.265a11.46 11.46 0 0 1 2.212 4.667l-.978.213-.976.214a9.46 9.46 0 0 0-1.826-3.853l-.18-.22-.062-.081a1 1 0 0 1 .184-1.328Z',
+})
diff --git a/src/components/icons/Heart2.tsx b/src/components/icons/Heart2.tsx
index 07f5a1d2c..9c9b0be5b 100644
--- a/src/components/icons/Heart2.tsx
+++ b/src/components/icons/Heart2.tsx
@@ -7,3 +7,7 @@ export const Heart2_Stroke2_Corner0_Rounded = createSinglePathSVG({
 export const Heart2_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
   path: 'M12.489 21.372c8.528-4.78 10.626-10.47 9.022-14.47-.779-1.941-2.414-3.333-4.342-3.763-1.697-.378-3.552.003-5.169 1.287-1.617-1.284-3.472-1.665-5.17-1.287-1.927.43-3.562 1.822-4.34 3.764-1.605 4 .493 9.69 9.021 14.47a1 1 0 0 0 .978 0Z',
 })
+
+export const LikeRepost_Stroke2_Corner2_Rounded = createSinglePathSVG({
+  path: 'M3.92 19v-4.153a1 1 0 0 1 1-1H9l.103.005a1 1 0 0 1 0 1.99L9 15.847H7.285c.854.737 1.784 1.38 2.631 1.9.702.431 1.329.769 1.78.997q.162.08.291.143a25.561 25.561 0 0 0 3.67-2.326c2.144-1.642 4.073-3.756 4.315-6.023a1 1 0 0 1 1.988.212c-.336 3.154-2.89 5.717-5.086 7.398a27.6 27.6 0 0 1-4.34 2.704l-.078.038-.021.01-.007.003-.002.001-.001.001a1 1 0 0 1-.827.01v0h-.002l-.004-.002-.013-.006q-.016-.006-.045-.02l-.162-.075a27.39 27.39 0 0 1-2.503-1.361 22 22 0 0 1-2.95-2.143V19a1 1 0 0 1-2 0ZM2 10c0-2.214.696-3.971 1.833-5.184A5.7 5.7 0 0 1 8 3a7.1 7.1 0 0 1 4 1.228A7.117 7.117 0 0 1 16 3c1.231 0 2.452.402 3.469 1.185l.031-1.702a1 1 0 0 1 2 .035l-.081 4.5a1 1 0 0 1-1 .983H16.5a1 1 0 1 1 0-2h2.02A3.68 3.68 0 0 0 16 5a5.12 5.12 0 0 0-3.11 1.053 3 3 0 0 0-.155.129l-.029.025v.002l-.003.002-.072.064a1 1 0 0 1-1.338-.068l-.028-.025a3 3 0 0 0-.155-.13A5.119 5.119 0 0 0 8 5c-.982 0-1.965.392-2.708 1.185C4.554 6.97 4 8.214 4 10q0 .507.099 1.002l.075.328.02.1a1 1 0 0 1-1.925.5l-.03-.097-.102-.446A7 7 0 0 1 2 10Z',
+})
diff --git a/src/components/icons/Phone.tsx b/src/components/icons/Phone.tsx
index 62000a1e5..8bfabc2a6 100644
--- a/src/components/icons/Phone.tsx
+++ b/src/components/icons/Phone.tsx
@@ -3,3 +3,7 @@ import {createSinglePathSVG} from './TEMPLATE'
 export const Phone_Stroke2_Corner0_Rounded = createSinglePathSVG({
   path: 'M5 4a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v16a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3V4Zm3-1a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H8Zm2 2a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z',
 })
+
+export const PhoneHaptic_Stroke2_Corner2_Rounded = createSinglePathSVG({
+  path: 'M16 6a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V6ZM2.87 7.225a1 1 0 0 1 1.337 1.482L3.155 9.759a.546.546 0 0 0-.05.714l.119.173c.52.827.52 1.88 0 2.707l-.12.174a.546.546 0 0 0 .051.714l1.052 1.052.069.076a1 1 0 0 1-1.407 1.406l-.076-.068-1.052-1.052a2.55 2.55 0 0 1-.237-3.328l.048-.075a.55.55 0 0 0 0-.504l-.048-.075a2.55 2.55 0 0 1 .237-3.328l1.052-1.052.076-.068Zm16.923.068a1 1 0 0 1 1.338-.068l.076.068 1.052 1.052.16.174c.696.837.78 2.03.209 2.958l-.133.196a.55.55 0 0 0 0 .654l.133.196a2.55 2.55 0 0 1-.21 2.958l-.16.174-1.05 1.052a1 1 0 1 1-1.415-1.414l1.052-1.052.064-.077a.55.55 0 0 0 .04-.552l-.053-.085a2.545 2.545 0 0 1 0-3.054l.052-.085a.55.55 0 0 0-.039-.552l-.064-.077-1.052-1.052-.068-.076a1 1 0 0 1 .068-1.338ZM13 6l.103.005a1 1 0 0 1 0 1.99L13 8h-2a1 1 0 1 1 0-2h2Zm5 12a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v12Z',
+})
diff --git a/src/components/icons/Repost.tsx b/src/components/icons/Repost.tsx
index 01214bca7..abf2c8ac2 100644
--- a/src/components/icons/Repost.tsx
+++ b/src/components/icons/Repost.tsx
@@ -11,3 +11,7 @@ export const Repost_Stroke2_Corner2_Rounded = createSinglePathSVG({
 export const Repost_Stroke2_Corner3_Rounded = createSinglePathSVG({
   path: 'M16.793 2.293a1 1 0 0 1 1.414 0L20.5 4.586a2 2 0 0 1 0 2.828l-2.293 2.293a1 1 0 0 1-1.414-1.414L18.086 7H7a2 2 0 0 0-2 2v2a1 1 0 1 1-2 0V9a4 4 0 0 1 4-4h11.086l-1.293-1.293a1 1 0 0 1 0-1.414ZM20 12a1 1 0 0 1 1 1v2a4 4 0 0 1-4 4H5.914l1.293 1.293a1 1 0 1 1-1.414 1.414L3.5 19.414a2 2 0 0 1 0-2.828l2.293-2.293a1 1 0 0 1 1.414 1.414L5.914 17H17a2 2 0 0 0 2-2v-2a1 1 0 0 1 1-1Z',
 })
+
+export const RepostRepost_Stroke2_Corner2_Rounded = createSinglePathSVG({
+  path: 'M6.043 14.293a1 1 0 1 1 1.414 1.414L5.164 18l2.293 2.293.068.076a1 1 0 0 1-1.406 1.406l-.076-.068-2.47-2.47a1.75 1.75 0 0 1 0-2.474l2.47-2.47Zm6.22 0a1 1 0 0 1 1.414 1.414L12.384 17H18a1 1 0 0 0 1-1v-3a1 1 0 1 1 2 0v3a3 3 0 0 1-3 3h-5.616l1.293 1.293.068.076a1 1 0 0 1-1.406 1.406l-.076-.068-2.47-2.47a1.75 1.75 0 0 1 0-2.474l2.47-2.47ZM3 11V8a3 3 0 0 1 3-3h5.586l-1.293-1.293-.068-.076a1 1 0 0 1 1.406-1.406l.076.068 2.47 2.47.12.133a1.75 1.75 0 0 1 0 2.209l-.12.132-2.47 2.47a1 1 0 1 1-1.414-1.414L11.586 7H6a1 1 0 0 0-1 1v3a1 1 0 1 1-2 0Zm13.543-8.707a1 1 0 0 1 1.338-.068l.076.068 2.47 2.47.12.133a1.75 1.75 0 0 1 0 2.209l-.12.132-2.47 2.47a1 1 0 1 1-1.414-1.414L18.836 6l-2.293-2.293-.068-.076a1 1 0 0 1 .068-1.338Z',
+})
diff --git a/src/lib/routes/router.ts b/src/lib/routes/router.ts
index ba76b1bda..c74192f29 100644
--- a/src/lib/routes/router.ts
+++ b/src/lib/routes/router.ts
@@ -1,8 +1,8 @@
 import {type Route, type RouteParams} from './types'
 
-export class Router {
+export class Router<T extends Record<string, any>> {
   routes: [string, Route][] = []
-  constructor(description: Record<string, string | string[]>) {
+  constructor(description: Record<keyof T, string | string[]>) {
     for (const [screen, pattern] of Object.entries(description)) {
       if (typeof pattern === 'string') {
         this.routes.push([screen, createRoute(pattern)])
@@ -14,7 +14,7 @@ export class Router {
     }
   }
 
-  matchName(name: string): Route | undefined {
+  matchName(name: keyof T | (string & {})): Route | undefined {
     for (const [screenName, route] of this.routes) {
       if (screenName === name) {
         return route
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index f58742390..c92be34c2 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -52,7 +52,18 @@ export type CommonNavigatorParams = {
   AccountSettings: undefined
   PrivacyAndSecuritySettings: undefined
   ContentAndMediaSettings: undefined
-  SettingsInterests: undefined
+  NotificationSettings: undefined
+  ReplyNotificationSettings: undefined
+  MentionNotificationSettings: undefined
+  QuoteNotificationSettings: undefined
+  LikeNotificationSettings: undefined
+  RepostNotificationSettings: undefined
+  NewFollowerNotificationSettings: undefined
+  LikesOnRepostsNotificationSettings: undefined
+  RepostsOnRepostsNotificationSettings: undefined
+  ActivityNotificationSettings: undefined
+  MiscellaneousNotificationSettings: undefined
+  InterestsSettings: undefined
   AboutSettings: undefined
   AppIconSettings: undefined
   Search: {q?: string}
@@ -61,7 +72,7 @@ export type CommonNavigatorParams = {
   MessagesConversation: {conversation: string; embed?: string; accept?: true}
   MessagesSettings: undefined
   MessagesInbox: undefined
-  NotificationSettings: undefined
+  LegacyNotificationSettings: undefined
   Feeds: undefined
   Start: {name: string; rkey: string}
   StarterPack: {name: string; rkey: string; new?: boolean}
@@ -104,8 +115,6 @@ export type FlatNavigatorParams = CommonNavigatorParams & {
   Search: {q?: string}
   Feeds: undefined
   Notifications: undefined
-  Hashtag: {tag: string; author?: string}
-  Topic: {topic: string}
   Messages: {pushToConversation?: string; animation?: 'push' | 'pop'}
 }
 
@@ -118,15 +127,8 @@ export type AllNavigatorParams = CommonNavigatorParams & {
   NotificationsTab: undefined
   Notifications: undefined
   MyProfileTab: undefined
-  Hashtag: {tag: string; author?: string}
-  Topic: {topic: string}
   MessagesTab: undefined
   Messages: {animation?: 'push' | 'pop'}
-  Start: {name: string; rkey: string}
-  StarterPack: {name: string; rkey: string; new?: boolean}
-  StarterPackShort: {code: string}
-  StarterPackWizard: undefined
-  StarterPackEdit: {rkey?: string}
 }
 
 // NOTE
diff --git a/src/routes.ts b/src/routes.ts
index 60bb65dd5..b66a0ae53 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -1,11 +1,17 @@
 import {Router} from '#/lib/routes/router'
+import {type FlatNavigatorParams} from './lib/routes/types'
 
-export const router = new Router({
+type AllNavigatableRoutes = Omit<
+  FlatNavigatorParams,
+  'NotFound' | 'SharedPreferencesTester'
+>
+
+export const router = new Router<AllNavigatableRoutes>({
   Home: '/',
   Search: '/search',
   Feeds: '/feeds',
   Notifications: '/notifications',
-  NotificationSettings: '/notifications/settings',
+  LegacyNotificationSettings: '/notifications/settings',
   Settings: '/settings',
   Lists: '/lists',
   // moderation
@@ -42,13 +48,25 @@ export const router = new Router({
   AccessibilitySettings: '/settings/accessibility',
   AppearanceSettings: '/settings/appearance',
   SavedFeeds: '/settings/saved-feeds',
-  // new settings
   AccountSettings: '/settings/account',
   PrivacyAndSecuritySettings: '/settings/privacy-and-security',
   ContentAndMediaSettings: '/settings/content-and-media',
-  SettingsInterests: '/settings/interests',
+  InterestsSettings: '/settings/interests',
   AboutSettings: '/settings/about',
   AppIconSettings: '/settings/app-icon',
+  NotificationSettings: '/settings/notifications',
+  ReplyNotificationSettings: '/settings/notifications/replies',
+  MentionNotificationSettings: '/settings/notifications/mentions',
+  QuoteNotificationSettings: '/settings/notifications/quotes',
+  LikeNotificationSettings: '/settings/notifications/likes',
+  RepostNotificationSettings: '/settings/notifications/reposts',
+  NewFollowerNotificationSettings: '/settings/notifications/new-followers',
+  LikesOnRepostsNotificationSettings:
+    '/settings/notifications/likes-on-reposts',
+  RepostsOnRepostsNotificationSettings:
+    '/settings/notifications/reposts-on-reposts',
+  ActivityNotificationSettings: '/settings/notifications/activity',
+  MiscellaneousNotificationSettings: '/settings/notifications/miscellaneous',
   // support
   Support: '/support',
   PrivacyPolicy: '/support/privacy',
diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx
index f37e7a9ba..0b8c88b9d 100644
--- a/src/screens/Messages/Settings.tsx
+++ b/src/screens/Messages/Settings.tsx
@@ -2,9 +2,9 @@ import {useCallback} from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
-import {NativeStackScreenProps} from '@react-navigation/native-stack'
+import {type NativeStackScreenProps} from '@react-navigation/native-stack'
 
-import {CommonNavigatorParams} from '#/lib/routes/types'
+import {type CommonNavigatorParams} from '#/lib/routes/types'
 import {isNative} from '#/platform/detection'
 import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration'
 import {useProfileQuery} from '#/state/queries/profile'
diff --git a/src/screens/Settings/SettingsInterests.tsx b/src/screens/Settings/InterestsSettings.tsx
index 42259e9b6..746315f7b 100644
--- a/src/screens/Settings/SettingsInterests.tsx
+++ b/src/screens/Settings/InterestsSettings.tsx
@@ -2,9 +2,11 @@ import {useMemo, useState} from 'react'
 import {type TextStyle, View, type ViewStyle} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
+import {type NativeStackScreenProps} from '@react-navigation/native-stack'
 import {useQueryClient} from '@tanstack/react-query'
 import debounce from 'lodash.debounce'
 
+import {type CommonNavigatorParams} from '#/lib/routes/types'
 import {
   preferencesQueryKey,
   usePreferencesQuery,
@@ -24,7 +26,8 @@ import * as Layout from '#/components/Layout'
 import {Loader} from '#/components/Loader'
 import {Text} from '#/components/Typography'
 
-export function SettingsInterests() {
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'InterestsSettings'>
+export function InterestsSettingsScreen({}: Props) {
   const t = useTheme()
   const gutters = useGutters(['base'])
   const {data: preferences} = usePreferencesQuery()
diff --git a/src/screens/Settings/LegacyNotificationSettings.tsx b/src/screens/Settings/LegacyNotificationSettings.tsx
new file mode 100644
index 000000000..a9ef5d983
--- /dev/null
+++ b/src/screens/Settings/LegacyNotificationSettings.tsx
@@ -0,0 +1,21 @@
+import {useCallback} from 'react'
+import {useFocusEffect} from '@react-navigation/native'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'LegacyNotificationSettings'
+>
+export function LegacyNotificationSettingsScreen({navigation}: Props) {
+  useFocusEffect(
+    useCallback(() => {
+      navigation.replace('NotificationSettings')
+    }, [navigation]),
+  )
+
+  return null
+}
diff --git a/src/screens/Settings/NotificationSettings.tsx b/src/screens/Settings/NotificationSettings.tsx
deleted file mode 100644
index ebb230c2c..000000000
--- a/src/screens/Settings/NotificationSettings.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import {Text} from 'react-native'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {AllNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
-import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
-import {useNotificationSettingsMutation} from '#/state/queries/notifications/settings'
-import {atoms as a} from '#/alf'
-import {Admonition} from '#/components/Admonition'
-import {Error} from '#/components/Error'
-import * as Toggle from '#/components/forms/Toggle'
-import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker'
-import * as Layout from '#/components/Layout'
-import {Loader} from '#/components/Loader'
-import * as SettingsList from './components/SettingsList'
-
-type Props = NativeStackScreenProps<AllNavigatorParams, 'NotificationSettings'>
-export function NotificationSettingsScreen({}: Props) {
-  const {_} = useLingui()
-
-  const {
-    data,
-    isError: isQueryError,
-    refetch,
-  } = useNotificationFeedQuery({
-    filter: 'all',
-  })
-  const serverPriority = data?.pages.at(0)?.priority
-
-  const {
-    mutate: onChangePriority,
-    isPending: isMutationPending,
-    variables,
-  } = useNotificationSettingsMutation()
-
-  const priority = isMutationPending
-    ? variables[0] === 'enabled'
-    : serverPriority
-
-  return (
-    <Layout.Screen>
-      <Layout.Header.Outer>
-        <Layout.Header.BackButton />
-        <Layout.Header.Content>
-          <Layout.Header.TitleText>
-            <Trans>Notification Settings</Trans>
-          </Layout.Header.TitleText>
-        </Layout.Header.Content>
-        <Layout.Header.Slot />
-      </Layout.Header.Outer>
-      <Layout.Content>
-        {isQueryError ? (
-          <Error
-            title={_(msg`Oops!`)}
-            message={_(msg`Something went wrong!`)}
-            onRetry={refetch}
-            sideBorders={false}
-          />
-        ) : (
-          <SettingsList.Container>
-            <SettingsList.Group>
-              <SettingsList.ItemIcon icon={BeakerIcon} />
-              <SettingsList.ItemText>
-                <Trans>Notification filters</Trans>
-              </SettingsList.ItemText>
-              <Toggle.Group
-                label={_(msg`Priority notifications`)}
-                type="checkbox"
-                values={priority ? ['enabled'] : []}
-                onChange={onChangePriority}
-                disabled={typeof priority !== 'boolean' || isMutationPending}>
-                <Toggle.Item
-                  name="enabled"
-                  label={_(msg`Enable priority notifications`)}
-                  style={[a.flex_1, a.justify_between]}>
-                  <Toggle.LabelText>
-                    <Trans>Enable priority notifications</Trans>
-                  </Toggle.LabelText>
-                  {!data ? <Loader size="md" /> : <Toggle.Platform />}
-                </Toggle.Item>
-              </Toggle.Group>
-            </SettingsList.Group>
-            <SettingsList.Item>
-              <Admonition type="warning" style={[a.flex_1]}>
-                <Trans>
-                  <Text style={[a.font_bold]}>Experimental:</Text> When this
-                  preference is enabled, you'll only receive reply and quote
-                  notifications from users you follow. We'll continue to add
-                  more controls here over time.
-                </Trans>
-              </Admonition>
-            </SettingsList.Item>
-          </SettingsList.Container>
-        )}
-      </Layout.Content>
-    </Layout.Screen>
-  )
-}
diff --git a/src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx
new file mode 100644
index 000000000..f726ab558
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx
@@ -0,0 +1,60 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {Heart2_Stroke2_Corner0_Rounded as HeartIcon} from '#/components/icons/Heart2'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'LikeNotificationSettings'
+>
+export function LikeNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={HeartIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>Likes</Trans>}
+              subtitleText={
+                <Trans>Get notifications when people like your posts.</Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls name="like" preference={preferences?.like} />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx
new file mode 100644
index 000000000..08a05d468
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx
@@ -0,0 +1,65 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {LikeRepost_Stroke2_Corner2_Rounded as LikeRepostIcon} from '#/components/icons/Heart2'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'LikesOnRepostsNotificationSettings'
+>
+export function LikesOnRepostsNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={LikeRepostIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>Likes on your reposts</Trans>}
+              subtitleText={
+                <Trans>
+                  Get notifications when people like posts that you've reposted.
+                </Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls
+              name="likeViaRepost"
+              preference={preferences?.likeViaRepost}
+            />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/MentionNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/MentionNotificationSettings.tsx
new file mode 100644
index 000000000..0a770157e
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/MentionNotificationSettings.tsx
@@ -0,0 +1,63 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'MentionNotificationSettings'
+>
+export function MentionNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={AtIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>Mentions</Trans>}
+              subtitleText={
+                <Trans>Get notifications when people mention you.</Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls
+              name="mention"
+              preference={preferences?.mention}
+            />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/MiscellaneousNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/MiscellaneousNotificationSettings.tsx
new file mode 100644
index 000000000..a0fe65ecf
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/MiscellaneousNotificationSettings.tsx
@@ -0,0 +1,68 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'MiscellaneousNotificationSettings'
+>
+export function MiscellaneousNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={ShapesIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>Everything else</Trans>}
+              subtitleText={
+                <Trans>
+                  Notifications for everything else, such as when someone joins
+                  via one of your starter packs.
+                </Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls
+              name="starterpackJoined"
+              preference={preferences?.starterpackJoined}
+              syncOthers={['verified', 'unverified']}
+              allowDisableInApp={false}
+            />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/NewFollowerNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/NewFollowerNotificationSettings.tsx
new file mode 100644
index 000000000..dd603a52f
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/NewFollowerNotificationSettings.tsx
@@ -0,0 +1,63 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {PersonPlus_Stroke2_Corner2_Rounded as PersonPlusIcon} from '#/components/icons/Person'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'NewFollowerNotificationSettings'
+>
+export function NewFollowerNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={PersonPlusIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>New followers</Trans>}
+              subtitleText={
+                <Trans>Get notifications when people follow you.</Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls
+              name="follow"
+              preference={preferences?.follow}
+            />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx
new file mode 100644
index 000000000..afb3df90f
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx
@@ -0,0 +1,60 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {CloseQuote_Stroke2_Corner0_Rounded as CloseQuoteIcon} from '#/components/icons/Quote'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'QuoteNotificationSettings'
+>
+export function QuoteNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={CloseQuoteIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>Quotes</Trans>}
+              subtitleText={
+                <Trans>Get notifications when people quote your posts.</Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls name="quote" preference={preferences?.quote} />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx
new file mode 100644
index 000000000..b3e7c6cff
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx
@@ -0,0 +1,66 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'ReplyNotificationSettings'
+>
+export function ReplyNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={BubbleIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>Replies</Trans>}
+              subtitleText={
+                <Trans>
+                  Get notifications when people reply to your posts.
+                </Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls
+              name="reply"
+              preference={preferences?.reply}
+              allowDisableInApp={false}
+            />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx
new file mode 100644
index 000000000..aa9e4e32f
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx
@@ -0,0 +1,63 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'RepostNotificationSettings'
+>
+export function RepostNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={RepostIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>Reposts</Trans>}
+              subtitleText={
+                <Trans>Get notifications when people repost your posts.</Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls
+              name="repost"
+              preference={preferences?.repost}
+            />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx
new file mode 100644
index 000000000..13fec6168
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx
@@ -0,0 +1,66 @@
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {RepostRepost_Stroke2_Corner2_Rounded as RepostRepostIcon} from '#/components/icons/Repost'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+import {PreferenceControls} from './components/PreferenceControls'
+
+type Props = NativeStackScreenProps<
+  AllNavigatorParams,
+  'RepostsOnRepostsNotificationSettings'
+>
+export function RepostsOnRepostsNotificationSettingsScreen({}: Props) {
+  const {data: preferences, isError} = useNotificationSettingsQuery()
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          <SettingsList.Item style={[a.align_start]}>
+            <SettingsList.ItemIcon icon={RepostRepostIcon} />
+            <ItemTextWithSubtitle
+              bold
+              titleText={<Trans>Reposts of your reposts</Trans>}
+              subtitleText={
+                <Trans>
+                  Get notifications when people repost posts that you've
+                  reposted.
+                </Trans>
+              }
+            />
+          </SettingsList.Item>
+          {isError ? (
+            <View style={[a.px_lg, a.pt_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          ) : (
+            <PreferenceControls
+              name="repostViaRepost"
+              preference={preferences?.repostViaRepost}
+            />
+          )}
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/components/ItemTextWithSubtitle.tsx b/src/screens/Settings/NotificationSettings/components/ItemTextWithSubtitle.tsx
new file mode 100644
index 000000000..217fc33b9
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/components/ItemTextWithSubtitle.tsx
@@ -0,0 +1,34 @@
+import {View} from 'react-native'
+
+import {atoms as a, useTheme} from '#/alf'
+import * as Skele from '#/components/Skeleton'
+import {Text} from '#/components/Typography'
+import * as SettingsList from '../../components/SettingsList'
+
+export function ItemTextWithSubtitle({
+  titleText,
+  subtitleText,
+  bold = false,
+  showSkeleton = false,
+}: {
+  titleText: React.ReactNode
+  subtitleText: React.ReactNode
+  bold?: boolean
+  showSkeleton?: boolean
+}) {
+  const t = useTheme()
+  return (
+    <View style={[a.flex_1, bold ? a.gap_xs : a.gap_2xs]}>
+      <SettingsList.ItemText style={bold && [a.font_bold, a.text_lg]}>
+        {titleText}
+      </SettingsList.ItemText>
+      {showSkeleton ? (
+        <Skele.Text style={[a.text_sm, {width: 120}]} />
+      ) : (
+        <Text style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_snug]}>
+          {subtitleText}
+        </Text>
+      )}
+    </View>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx b/src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx
new file mode 100644
index 000000000..336e08695
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx
@@ -0,0 +1,194 @@
+import {useMemo} from 'react'
+import {View} from 'react-native'
+import {type AppBskyNotificationDefs} from '@atproto/api'
+import {type FilterablePreference} from '@atproto/api/dist/client/types/app/bsky/notification/defs'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {useNotificationSettingsUpdateMutation} from '#/state/queries/notifications/settings'
+import {atoms as a, platform, useTheme} from '#/alf'
+import * as Toggle from '#/components/forms/Toggle'
+import {Loader} from '#/components/Loader'
+import {Text} from '#/components/Typography'
+import {Divider} from '../../components/SettingsList'
+
+export function PreferenceControls({
+  name,
+  syncOthers,
+  preference,
+  allowDisableInApp = true,
+}: {
+  name: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>
+  /**
+   * Keep other prefs in sync with `name`. For use in the "everything else" category
+   * which groups starterpack joins + verified + unverified notifications into a single toggle.
+   */
+  syncOthers?: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>[]
+  preference?: AppBskyNotificationDefs.Preference | FilterablePreference
+  allowDisableInApp?: boolean
+}) {
+  if (!preference)
+    return (
+      <View style={[a.w_full, a.pt_5xl, a.align_center]}>
+        <Loader size="xl" />
+      </View>
+    )
+
+  return (
+    <Inner
+      name={name}
+      syncOthers={syncOthers}
+      preference={preference}
+      allowDisableInApp={allowDisableInApp}
+    />
+  )
+}
+
+export function Inner({
+  name,
+  syncOthers = [],
+  preference,
+  allowDisableInApp,
+}: {
+  name: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>
+  syncOthers?: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>[]
+  preference: AppBskyNotificationDefs.Preference | FilterablePreference
+  allowDisableInApp: boolean
+}) {
+  const t = useTheme()
+  const {_} = useLingui()
+  const {mutate} = useNotificationSettingsUpdateMutation()
+
+  const channels = useMemo(() => {
+    const arr = []
+    if (preference.list) arr.push('list')
+    if (preference.push) arr.push('push')
+    return arr
+  }, [preference])
+
+  const onChangeChannels = (change: string[]) => {
+    const newPreference = {
+      ...preference,
+      list: change.includes('list'),
+      push: change.includes('push'),
+    } satisfies typeof preference
+
+    mutate({
+      [name]: newPreference,
+      ...Object.fromEntries(syncOthers.map(key => [key, newPreference])),
+    })
+  }
+
+  const onChangeFilter = ([change]: string[]) => {
+    if (change !== 'all' && change !== 'follows')
+      throw new Error('Invalid filter')
+
+    const newPreference = {
+      ...preference,
+      filter: change,
+    } satisfies typeof preference
+
+    mutate({
+      [name]: newPreference,
+      ...Object.fromEntries(syncOthers.map(key => [key, newPreference])),
+    })
+  }
+
+  return (
+    <View style={[a.px_xl, a.pt_md, a.gap_sm]}>
+      <Toggle.Group
+        type="checkbox"
+        label={_(`Select your preferred notification channels`)}
+        values={channels}
+        onChange={onChangeChannels}>
+        <View style={[a.gap_sm]}>
+          <Toggle.Item
+            label={_(msg`Receive push notifications`)}
+            name="push"
+            style={[
+              a.py_xs,
+              platform({
+                native: [a.justify_between],
+                web: [a.flex_row_reverse, a.gap_md],
+              }),
+            ]}>
+            <Toggle.LabelText
+              style={[t.atoms.text, a.font_normal, a.text_md, a.flex_1]}>
+              <Trans>Push notifications</Trans>
+            </Toggle.LabelText>
+            <Toggle.Platform />
+          </Toggle.Item>
+          {allowDisableInApp && (
+            <Toggle.Item
+              label={_(msg`Receive in-app notifications`)}
+              name="list"
+              style={[
+                a.py_xs,
+                platform({
+                  native: [a.justify_between],
+                  web: [a.flex_row_reverse, a.gap_md],
+                }),
+              ]}>
+              <Toggle.LabelText
+                style={[t.atoms.text, a.font_normal, a.text_md, a.flex_1]}>
+                <Trans>In-app notifications</Trans>
+              </Toggle.LabelText>
+              <Toggle.Platform />
+            </Toggle.Item>
+          )}
+        </View>
+      </Toggle.Group>
+      {'filter' in preference && (
+        <>
+          <Divider />
+          <Text style={[a.font_bold, a.text_md]}>From</Text>
+          <Toggle.Group
+            type="radio"
+            label={_('Filter who you receive notifications from')}
+            values={[preference.filter]}
+            onChange={onChangeFilter}
+            disabled={channels.length === 0}>
+            <View style={[a.gap_sm]}>
+              <Toggle.Item
+                label={_(msg`Everyone`)}
+                name="all"
+                style={[
+                  a.flex_row,
+                  a.py_xs,
+                  platform({native: [a.gap_sm], web: [a.gap_md]}),
+                ]}>
+                <Toggle.Radio />
+                <Toggle.LabelText
+                  style={[
+                    channels.length > 0 && t.atoms.text,
+                    a.font_normal,
+                    a.text_md,
+                  ]}>
+                  <Trans>Everyone</Trans>
+                </Toggle.LabelText>
+              </Toggle.Item>
+              <Toggle.Item
+                label={_(msg`People I follow`)}
+                name="follows"
+                style={[
+                  a.flex_row,
+                  a.py_xs,
+                  platform({native: [a.gap_sm], web: [a.gap_md]}),
+                ]}>
+                <Toggle.Radio />
+                <Toggle.LabelText
+                  style={[
+                    channels.length > 0 && t.atoms.text,
+                    a.font_normal,
+                    a.text_md,
+                  ]}>
+                  <Trans>People I follow</Trans>
+                </Toggle.LabelText>
+              </Toggle.Item>
+            </View>
+          </Toggle.Group>
+        </>
+      )}
+    </View>
+  )
+}
diff --git a/src/screens/Settings/NotificationSettings/index.tsx b/src/screens/Settings/NotificationSettings/index.tsx
new file mode 100644
index 000000000..a4f6dede0
--- /dev/null
+++ b/src/screens/Settings/NotificationSettings/index.tsx
@@ -0,0 +1,293 @@
+import {useEffect} from 'react'
+import {Linking, View} from 'react-native'
+import * as Notification from 'expo-notifications'
+import {type AppBskyNotificationDefs} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useQuery, useQueryClient} from '@tanstack/react-query'
+
+import {useAppState} from '#/lib/hooks/useAppState'
+import {
+  type AllNavigatorParams,
+  type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {isAndroid, isIOS, isWeb} from '#/platform/detection'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
+import {atoms as a} from '#/alf'
+import {Admonition} from '#/components/Admonition'
+import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At'
+// import {BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging'
+import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble'
+import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic'
+import {
+  Heart2_Stroke2_Corner0_Rounded as HeartIcon,
+  LikeRepost_Stroke2_Corner2_Rounded as LikeRepostIcon,
+} from '#/components/icons/Heart2'
+import {PersonPlus_Stroke2_Corner2_Rounded as PersonPlusIcon} from '#/components/icons/Person'
+import {CloseQuote_Stroke2_Corner0_Rounded as CloseQuoteIcon} from '#/components/icons/Quote'
+import {
+  Repost_Stroke2_Corner2_Rounded as RepostIcon,
+  RepostRepost_Stroke2_Corner2_Rounded as RepostRepostIcon,
+} from '#/components/icons/Repost'
+import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes'
+import * as Layout from '#/components/Layout'
+import * as SettingsList from '../components/SettingsList'
+import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
+
+const RQKEY = ['notification-permissions']
+
+type Props = NativeStackScreenProps<AllNavigatorParams, 'NotificationSettings'>
+export function NotificationSettingsScreen({}: Props) {
+  const {_} = useLingui()
+  const queryClient = useQueryClient()
+  const {data: settings, isError} = useNotificationSettingsQuery()
+
+  const {data: permissions, refetch} = useQuery({
+    queryKey: RQKEY,
+    queryFn: async () => {
+      if (isWeb) return null
+      return await Notification.getPermissionsAsync()
+    },
+  })
+
+  const appState = useAppState()
+  useEffect(() => {
+    if (appState === 'active') {
+      refetch()
+    }
+  }, [appState, refetch])
+
+  const onRequestPermissions = async () => {
+    if (isWeb) return
+    if (permissions?.canAskAgain) {
+      const response = await Notification.requestPermissionsAsync()
+      queryClient.setQueryData(RQKEY, response)
+    } else {
+      if (isAndroid) {
+        try {
+          await Linking.sendIntent(
+            'android.settings.APP_NOTIFICATION_SETTINGS',
+            [
+              {
+                key: 'android.provider.extra.APP_PACKAGE',
+                value: 'xyz.blueskyweb.app',
+              },
+            ],
+          )
+        } catch {
+          Linking.openSettings()
+        }
+      } else if (isIOS) {
+        Linking.openSettings()
+      }
+    }
+  }
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Notifications</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <SettingsList.Container>
+          {permissions && !permissions.granted && (
+            <>
+              <SettingsList.PressableItem
+                label={_(msg`Enable push notifications`)}
+                onPress={onRequestPermissions}>
+                <SettingsList.ItemIcon icon={HapticIcon} />
+                <SettingsList.ItemText>
+                  <Trans>Enable push notifications</Trans>
+                </SettingsList.ItemText>
+              </SettingsList.PressableItem>
+              <SettingsList.Divider />
+            </>
+          )}
+          {isError && (
+            <View style={[a.px_lg, a.pb_md]}>
+              <Admonition type="error">
+                <Trans>Failed to load notification settings.</Trans>
+              </Admonition>
+            </View>
+          )}
+          <View style={[a.gap_sm]}>
+            <SettingsList.LinkItem
+              label={_(msg`Settings for reply notifications`)}
+              to={{screen: 'ReplyNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={BubbleIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>Replies</Trans>}
+                subtitleText={<SettingPreview preference={settings?.reply} />}
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+            <SettingsList.LinkItem
+              label={_(msg`Settings for mention notifications`)}
+              to={{screen: 'MentionNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={AtIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>Mentions</Trans>}
+                subtitleText={<SettingPreview preference={settings?.mention} />}
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+            <SettingsList.LinkItem
+              label={_(msg`Settings for quote notifications`)}
+              to={{screen: 'QuoteNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={CloseQuoteIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>Quotes</Trans>}
+                subtitleText={<SettingPreview preference={settings?.quote} />}
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+            <SettingsList.LinkItem
+              label={_(msg`Settings for like notifications`)}
+              to={{screen: 'LikeNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={HeartIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>Likes</Trans>}
+                subtitleText={<SettingPreview preference={settings?.like} />}
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+            <SettingsList.LinkItem
+              label={_(msg`Settings for repost notifications`)}
+              to={{screen: 'RepostNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={RepostIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>Reposts</Trans>}
+                subtitleText={<SettingPreview preference={settings?.repost} />}
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+            <SettingsList.LinkItem
+              label={_(msg`Settings for new follower notifications`)}
+              to={{screen: 'NewFollowerNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={PersonPlusIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>New followers</Trans>}
+                subtitleText={<SettingPreview preference={settings?.follow} />}
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+            {/* <SettingsList.LinkItem
+              label={_(msg`Settings for activity alerts`)}
+              to={{screen: 'ActivityNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={BellRingingIcon} />
+
+              <ItemTextWithSubtitle
+                titleText={<Trans>Activity alerts</Trans>}
+                subtitleText={
+                  <SettingPreview preference={settings?.subscribedPost} />
+                }
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem> */}
+            <SettingsList.LinkItem
+              label={_(
+                msg`Settings for notifications for likes on your reposts`,
+              )}
+              to={{screen: 'LikesOnRepostsNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={LikeRepostIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>Likes on your reposts</Trans>}
+                subtitleText={
+                  <SettingPreview preference={settings?.likeViaRepost} />
+                }
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+            <SettingsList.LinkItem
+              label={_(
+                msg`Settings for notifications for reposts of your reposts`,
+              )}
+              to={{screen: 'RepostsOnRepostsNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={RepostRepostIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>Reposts of your reposts</Trans>}
+                subtitleText={
+                  <SettingPreview preference={settings?.repostViaRepost} />
+                }
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+            <SettingsList.LinkItem
+              label={_(msg`Settings for notifications for everything else`)}
+              to={{screen: 'MiscellaneousNotificationSettings'}}
+              contentContainerStyle={[a.align_start]}>
+              <SettingsList.ItemIcon icon={ShapesIcon} />
+              <ItemTextWithSubtitle
+                titleText={<Trans>Everything else</Trans>}
+                // technically a bundle of several settings, but since they're set together
+                // and are most likely in sync we'll just show the state of one of them
+                subtitleText={
+                  <SettingPreview preference={settings?.starterpackJoined} />
+                }
+                showSkeleton={!settings}
+              />
+            </SettingsList.LinkItem>
+          </View>
+        </SettingsList.Container>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
+
+function SettingPreview({
+  preference,
+}: {
+  preference?:
+    | AppBskyNotificationDefs.Preference
+    | AppBskyNotificationDefs.FilterablePreference
+}) {
+  const {_} = useLingui()
+  if (!preference) {
+    return null
+  } else {
+    if ('filter' in preference) {
+      if (preference.filter === 'all') {
+        if (preference.list && preference.push) {
+          return _(msg`In-app, Push, Everyone`)
+        } else if (preference.list) {
+          return _(msg`In-app, Everyone`)
+        } else if (preference.push) {
+          return _(msg`Push, Everyone`)
+        }
+      } else if (preference.filter === 'follows') {
+        if (preference.list && preference.push) {
+          return _(msg`In-app, Push, People you follow`)
+        } else if (preference.list) {
+          return _(msg`In-app, People you follow`)
+        } else if (preference.push) {
+          return _(msg`Push, People you follow`)
+        }
+      }
+    } else {
+      if (preference.list && preference.push) {
+        return _(msg`In-app, Push`)
+      } else if (preference.list) {
+        return _(msg`In-app`)
+      } else if (preference.push) {
+        return _(msg`Push`)
+      }
+    }
+  }
+
+  return _(msg`Off`)
+}
diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx
index 9f36c27ac..6310c7c3c 100644
--- a/src/screens/Settings/Settings.tsx
+++ b/src/screens/Settings/Settings.tsx
@@ -36,6 +36,7 @@ import {AvatarStackWithFetch} from '#/components/AvatarStack'
 import {useDialogControl} from '#/components/Dialog'
 import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount'
 import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility'
+import {Bell_Stroke2_Corner0_Rounded as NotificationIcon} from '#/components/icons/Bell'
 import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo'
 import {ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon} from '#/components/icons/Chevron'
 import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion'
@@ -181,6 +182,14 @@ export function SettingsScreen({}: Props) {
             </SettingsList.ItemText>
           </SettingsList.LinkItem>
           <SettingsList.LinkItem
+            to="/settings/notifications"
+            label={_(msg`Notifications`)}>
+            <SettingsList.ItemIcon icon={NotificationIcon} />
+            <SettingsList.ItemText>
+              <Trans>Notifications</Trans>
+            </SettingsList.ItemText>
+          </SettingsList.LinkItem>
+          <SettingsList.LinkItem
             to="/settings/content-and-media"
             label={_(msg`Content and media`)}>
             <SettingsList.ItemIcon icon={WindowIcon} />
diff --git a/src/state/queries/notifications/settings.ts b/src/state/queries/notifications/settings.ts
index 2ac42aa32..9661bed1b 100644
--- a/src/state/queries/notifications/settings.ts
+++ b/src/state/queries/notifications/settings.ts
@@ -1,72 +1,63 @@
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useMutation, useQueryClient} from '@tanstack/react-query'
+import {type AppBskyNotificationDefs} from '@atproto/api'
+import {t} from '@lingui/macro'
+import {
+  type QueryClient,
+  useMutation,
+  useQuery,
+  useQueryClient,
+} from '@tanstack/react-query'
 
-import {until} from '#/lib/async/until'
 import {logger} from '#/logger'
-import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
-import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread'
 import {useAgent} from '#/state/session'
 import * as Toast from '#/view/com/util/Toast'
 
-export function useNotificationSettingsMutation() {
-  const {_} = useLingui()
+const RQKEY_ROOT = 'notification-settings'
+const RQKEY = [RQKEY_ROOT]
+
+export function useNotificationSettingsQuery() {
+  const agent = useAgent()
+
+  return useQuery({
+    queryKey: RQKEY,
+    queryFn: async () => {
+      const response = await agent.app.bsky.notification.getPreferences()
+      return response.data.preferences
+    },
+  })
+}
+export function useNotificationSettingsUpdateMutation() {
   const agent = useAgent()
   const queryClient = useQueryClient()
 
   return useMutation({
-    mutationFn: async (keys: string[]) => {
-      const enabled = keys[0] === 'enabled'
-
-      await agent.api.app.bsky.notification.putPreferences({
-        priority: enabled,
-      })
-
-      await until(
-        5, // 5 tries
-        1e3, // 1s delay between tries
-        res => res.data.priority === enabled,
-        () => agent.api.app.bsky.notification.listNotifications({limit: 1}),
-      )
-
-      eagerlySetCachedPriority(queryClient, enabled)
-    },
-    onError: err => {
-      logger.error('Failed to save notification preferences', {
-        safeMessage: err,
-      })
-      Toast.show(
-        _(msg`Failed to save notification preferences, please try again`),
-        'xmark',
+    mutationFn: async (
+      update: Partial<AppBskyNotificationDefs.Preferences>,
+    ) => {
+      const response = await agent.app.bsky.notification.putPreferencesV2(
+        update,
       )
+      return response.data.preferences
     },
-    onSuccess: () => {
-      Toast.show(_(msg({message: 'Preference saved', context: 'toast'})))
+    onMutate: update => {
+      optimisticUpdateNotificationSettings(queryClient, update)
     },
-    onSettled: () => {
-      invalidateCachedUnreadPage()
-      queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS('all')})
-      queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS('mentions')})
+    onError: e => {
+      logger.error('Could not update notification settings', {message: e})
+      queryClient.invalidateQueries({queryKey: RQKEY})
+      Toast.show(t`Could not update notification settings`, 'xmark')
     },
   })
 }
 
-function eagerlySetCachedPriority(
-  queryClient: ReturnType<typeof useQueryClient>,
-  enabled: boolean,
+function optimisticUpdateNotificationSettings(
+  queryClient: QueryClient,
+  update: Partial<AppBskyNotificationDefs.Preferences>,
 ) {
-  function updateData(old: any) {
-    if (!old) return old
-    return {
-      ...old,
-      pages: old.pages.map((page: any) => {
-        return {
-          ...page,
-          priority: enabled,
-        }
-      }),
-    }
-  }
-  queryClient.setQueryData(RQKEY_NOTIFS('all'), updateData)
-  queryClient.setQueryData(RQKEY_NOTIFS('mentions'), updateData)
+  queryClient.setQueryData(
+    RQKEY,
+    (old?: AppBskyNotificationDefs.Preferences) => {
+      if (!old) return old
+      return {...old, ...update}
+    },
+  )
 }
diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx
index ace0de2ae..528d6be87 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -130,7 +130,7 @@ export function NotificationsScreen({}: Props) {
         </Layout.Header.Content>
         <Layout.Header.Slot>
           <Link
-            to="/notifications/settings"
+            to={{screen: 'NotificationSettings'}}
             label={_(msg`Notification settings`)}
             size="small"
             variant="ghost"