diff --git a/assets/icons/bellRinging_stroke2_corner0_rounded.svg b/assets/icons/bellRinging_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..d49a59a01a
--- /dev/null
+++ b/assets/icons/bellRinging_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/likeRepost_stroke2_corner2_rounded.svg b/assets/icons/likeRepost_stroke2_corner2_rounded.svg
new file mode 100644
index 0000000000..f5d2da35bc
--- /dev/null
+++ b/assets/icons/likeRepost_stroke2_corner2_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/phoneHaptic_stroke2_corner2_rounded.svg b/assets/icons/phoneHaptic_stroke2_corner2_rounded.svg
new file mode 100644
index 0000000000..ebcf89b46e
--- /dev/null
+++ b/assets/icons/phoneHaptic_stroke2_corner2_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/repostRepost_stroke2_corner2_rounded.svg b/assets/icons/repostRepost_stroke2_corner2_rounded.svg
new file mode 100644
index 0000000000..caec8c1029
--- /dev/null
+++ b/assets/icons/repostRepost_stroke2_corner2_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go
index f419212cc9..ef796920d6 100644
--- a/bskyweb/cmd/bskyweb/server.go
+++ b/bskyweb/cmd/bskyweb/server.go
@@ -278,6 +278,17 @@ func serve(cctx *cli.Context) error {
e.GET("/settings/content-and-media", server.WebGeneric)
e.GET("/settings/interests", server.WebGeneric)
e.GET("/settings/about", server.WebGeneric)
+ e.GET("/settings/notifications", server.WebGeneric)
+ e.GET("/settings/notifications/replies", server.WebGeneric)
+ e.GET("/settings/notifications/mentions", server.WebGeneric)
+ e.GET("/settings/notifications/quotes", server.WebGeneric)
+ e.GET("/settings/notifications/likes", server.WebGeneric)
+ e.GET("/settings/notifications/reposts", server.WebGeneric)
+ e.GET("/settings/notifications/new-followers", server.WebGeneric)
+ e.GET("/settings/notifications/likes-on-reposts", server.WebGeneric)
+ e.GET("/settings/notifications/reposts-on-reposts", server.WebGeneric)
+ e.GET("/settings/notifications/activity", server.WebGeneric)
+ e.GET("/settings/notifications/miscellaneous", server.WebGeneric)
e.GET("/settings/app-icon", server.WebGeneric)
e.GET("/sys/debug", server.WebGeneric)
e.GET("/sys/debug-mod", server.WebGeneric)
diff --git a/package.json b/package.json
index 691a6e94d0..fe38554141 100644
--- a/package.json
+++ b/package.json
@@ -218,7 +218,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
- "@atproto/dev-env": "^0.3.133",
+ "@atproto/dev-env": "^0.3.142",
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.26.0",
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index 2f26c09711..3bf1ace852 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()
@@ -380,6 +390,83 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
requireAuth: true,
}}
/>
+ NotificationSettingsScreen}
+ options={{title: title(msg`Notification settings`), requireAuth: true}}
+ />
+ ReplyNotificationSettingsScreen}
+ options={{
+ title: title(msg`Reply notifications`),
+ requireAuth: true,
+ }}
+ />
+ MentionNotificationSettingsScreen}
+ options={{
+ title: title(msg`Mention notifications`),
+ requireAuth: true,
+ }}
+ />
+ QuoteNotificationSettingsScreen}
+ options={{
+ title: title(msg`Quote notifications`),
+ requireAuth: true,
+ }}
+ />
+ LikeNotificationSettingsScreen}
+ options={{
+ title: title(msg`Like notifications`),
+ requireAuth: true,
+ }}
+ />
+ RepostNotificationSettingsScreen}
+ options={{
+ title: title(msg`Repost notifications`),
+ requireAuth: true,
+ }}
+ />
+ NewFollowerNotificationSettingsScreen}
+ options={{
+ title: title(msg`New follower notifications`),
+ requireAuth: true,
+ }}
+ />
+ LikesOnRepostsNotificationSettingsScreen}
+ options={{
+ title: title(msg`Likes on your reposts notifications`),
+ requireAuth: true,
+ }}
+ />
+ RepostsOnRepostsNotificationSettingsScreen}
+ options={{
+ title: title(msg`Reposts on your reposts notifications`),
+ requireAuth: true,
+ }}
+ />
+ MiscellaneousNotificationSettingsScreen}
+ options={{
+ title: title(msg`Miscellaneous notifications`),
+ requireAuth: true,
+ }}
+ />
ContentAndMediaSettingsScreen}
@@ -389,8 +476,8 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
}}
/>
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}}
/>
NotificationSettingsScreen}
+ name="LegacyNotificationSettings"
+ getComponent={() => LegacyNotificationSettingsScreen}
options={{title: title(msg`Notification settings`), requireAuth: true}}
/>
> {
routes: [string, Route][] = []
- constructor(description: Record) {
+ constructor(description: Record) {
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 f587423900..c92be34c23 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 60bb65dd5a..b66a0ae53f 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({
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 f37e7a9ba1..0b8c88b9dd 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
similarity index 96%
rename from src/screens/Settings/SettingsInterests.tsx
rename to src/screens/Settings/InterestsSettings.tsx
index 42259e9b68..746315f7b8 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
+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 0000000000..a9ef5d9831
--- /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 ebb230c2ca..0000000000
--- 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
-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 (
-
-
-
-
-
- Notification Settings
-
-
-
-
-
- {isQueryError ? (
-
- ) : (
-
-
-
-
- Notification filters
-
-
-
-
- Enable priority notifications
-
- {!data ? : }
-
-
-
-
-
-
- Experimental: 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.
-
-
-
-
- )}
-
-
- )
-}
diff --git a/src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx
new file mode 100644
index 0000000000..f726ab558a
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ Likes}
+ subtitleText={
+ Get notifications when people like your posts.
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx
new file mode 100644
index 0000000000..08a05d468f
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ Likes on your reposts}
+ subtitleText={
+
+ Get notifications when people like posts that you've reposted.
+
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/MentionNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/MentionNotificationSettings.tsx
new file mode 100644
index 0000000000..0a770157e9
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ Mentions}
+ subtitleText={
+ Get notifications when people mention you.
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/MiscellaneousNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/MiscellaneousNotificationSettings.tsx
new file mode 100644
index 0000000000..a0fe65ecf6
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ Everything else}
+ subtitleText={
+
+ Notifications for everything else, such as when someone joins
+ via one of your starter packs.
+
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/NewFollowerNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/NewFollowerNotificationSettings.tsx
new file mode 100644
index 0000000000..dd603a52f5
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ New followers}
+ subtitleText={
+ Get notifications when people follow you.
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx
new file mode 100644
index 0000000000..afb3df90f5
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ Quotes}
+ subtitleText={
+ Get notifications when people quote your posts.
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx
new file mode 100644
index 0000000000..b3e7c6cff2
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ Replies}
+ subtitleText={
+
+ Get notifications when people reply to your posts.
+
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx
new file mode 100644
index 0000000000..aa9e4e32fa
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ Reposts}
+ subtitleText={
+ Get notifications when people repost your posts.
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx
new file mode 100644
index 0000000000..13fec61682
--- /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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+ Reposts of your reposts}
+ subtitleText={
+
+ Get notifications when people repost posts that you've
+ reposted.
+
+ }
+ />
+
+ {isError ? (
+
+
+ Failed to load notification settings.
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/components/ItemTextWithSubtitle.tsx b/src/screens/Settings/NotificationSettings/components/ItemTextWithSubtitle.tsx
new file mode 100644
index 0000000000..217fc33b95
--- /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 (
+
+
+ {titleText}
+
+ {showSkeleton ? (
+
+ ) : (
+
+ {subtitleText}
+
+ )}
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx b/src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx
new file mode 100644
index 0000000000..336e086950
--- /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
+ /**
+ * 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[]
+ preference?: AppBskyNotificationDefs.Preference | FilterablePreference
+ allowDisableInApp?: boolean
+}) {
+ if (!preference)
+ return (
+
+
+
+ )
+
+ return (
+
+ )
+}
+
+export function Inner({
+ name,
+ syncOthers = [],
+ preference,
+ allowDisableInApp,
+}: {
+ name: Exclude
+ syncOthers?: Exclude[]
+ 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 (
+
+
+
+
+
+ Push notifications
+
+
+
+ {allowDisableInApp && (
+
+
+ In-app notifications
+
+
+
+ )}
+
+
+ {'filter' in preference && (
+ <>
+
+ From
+
+
+
+
+ 0 && t.atoms.text,
+ a.font_normal,
+ a.text_md,
+ ]}>
+ Everyone
+
+
+
+
+ 0 && t.atoms.text,
+ a.font_normal,
+ a.text_md,
+ ]}>
+ People I follow
+
+
+
+
+ >
+ )}
+
+ )
+}
diff --git a/src/screens/Settings/NotificationSettings/index.tsx b/src/screens/Settings/NotificationSettings/index.tsx
new file mode 100644
index 0000000000..a4f6dede05
--- /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
+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 (
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+ {permissions && !permissions.granted && (
+ <>
+
+
+
+ Enable push notifications
+
+
+
+ >
+ )}
+ {isError && (
+
+
+ Failed to load notification settings.
+
+
+ )}
+
+
+
+ Replies}
+ subtitleText={}
+ showSkeleton={!settings}
+ />
+
+
+
+ Mentions}
+ subtitleText={}
+ showSkeleton={!settings}
+ />
+
+
+
+ Quotes}
+ subtitleText={}
+ showSkeleton={!settings}
+ />
+
+
+
+ Likes}
+ subtitleText={}
+ showSkeleton={!settings}
+ />
+
+
+
+ Reposts}
+ subtitleText={}
+ showSkeleton={!settings}
+ />
+
+
+
+ New followers}
+ subtitleText={}
+ showSkeleton={!settings}
+ />
+
+ {/*
+
+
+ Activity alerts}
+ subtitleText={
+
+ }
+ showSkeleton={!settings}
+ />
+ */}
+
+
+ Likes on your reposts}
+ subtitleText={
+
+ }
+ showSkeleton={!settings}
+ />
+
+
+
+ Reposts of your reposts}
+ subtitleText={
+
+ }
+ showSkeleton={!settings}
+ />
+
+
+
+ Everything else}
+ // 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={
+
+ }
+ showSkeleton={!settings}
+ />
+
+
+
+
+
+ )
+}
+
+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 9f36c27acc..6310c7c3c8 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'
@@ -180,6 +181,14 @@ export function SettingsScreen({}: Props) {
Moderation
+
+
+
+ Notifications
+
+
diff --git a/src/state/queries/notifications/settings.ts b/src/state/queries/notifications/settings.ts
index 2ac42aa328..9661bed1be 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}),
+ mutationFn: async (
+ update: Partial,
+ ) => {
+ const response = await agent.app.bsky.notification.putPreferencesV2(
+ update,
)
-
- eagerlySetCachedPriority(queryClient, enabled)
+ return response.data.preferences
},
- onError: err => {
- logger.error('Failed to save notification preferences', {
- safeMessage: err,
- })
- Toast.show(
- _(msg`Failed to save notification preferences, please try again`),
- 'xmark',
- )
+ onMutate: update => {
+ optimisticUpdateNotificationSettings(queryClient, update)
},
- onSuccess: () => {
- Toast.show(_(msg({message: 'Preference saved', context: 'toast'})))
- },
- 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,
- enabled: boolean,
+function optimisticUpdateNotificationSettings(
+ queryClient: QueryClient,
+ update: Partial,
) {
- 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 ace0de2aed..528d6be870 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -130,7 +130,7 @@ export function NotificationsScreen({}: Props) {