Refactor notification settings as dialogs (#10646)

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
DS Boyce
2026-05-29 09:35:14 -07:00
committed by GitHub
parent e52adeac12
commit ce4efc511b
21 changed files with 338 additions and 1082 deletions
-10
View File
@@ -308,16 +308,6 @@ func serve(cctx *cli.Context) error {
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("/sys/debug", server.WebGeneric)
e.GET("/sys/debug-mod", server.WebGeneric)
e.GET("/sys/log", server.WebGeneric)
+6 -95
View File
@@ -114,16 +114,6 @@ import {InterestsSettingsScreen} from '#/screens/Settings/InterestsSettings'
import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings'
import {LegacyNotificationSettingsScreen} from '#/screens/Settings/LegacyNotificationSettings'
import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
import {ActivityNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/ActivityNotificationSettings'
import {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'
import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings'
import {SettingsScreen} from '#/screens/Settings/Settings'
import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences'
@@ -444,86 +434,6 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
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 of your reposts notifications`),
requireAuth: true,
}}
/>
<Stack.Screen
name="RepostsOnRepostsNotificationSettings"
getComponent={() => RepostsOnRepostsNotificationSettingsScreen}
options={{
title: title(msg`Reposts of your reposts notifications`),
requireAuth: true,
}}
/>
<Stack.Screen
name="ActivityNotificationSettings"
getComponent={() => ActivityNotificationSettingsScreen}
options={{
title: title(msg`Activity notifications`),
requireAuth: true,
}}
/>
<Stack.Screen
name="MiscellaneousNotificationSettings"
getComponent={() => MiscellaneousNotificationSettingsScreen}
options={{
title: title(msg`Miscellaneous notifications`),
requireAuth: true,
}}
/>
<Stack.Screen
name="ContentAndMediaSettings"
getComponent={() => ContentAndMediaSettingsScreen}
@@ -916,6 +826,7 @@ const LINKING = {
function RoutesContainer({children}: React.PropsWithChildren<{}>) {
const ax = useAnalytics()
// eslint-disable-next-line react-compiler/react-compiler
const notyLogger = ax.logger.useChild(ax.logger.Context.Notifications)
const theme = useColorSchemeStyle(DefaultTheme, DarkTheme)
const {currentAccount, accounts} = useSession()
@@ -943,7 +854,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
const account = accounts.find(a => a.did === payload.recipientDid)
if (account) {
onPressSwitchAccount(account, 'Notification')
void onPressSwitchAccount(account, 'Notification')
} else {
setShowLoggedOut(true)
}
@@ -955,7 +866,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
// chat-added-to-group routes to the convo because the recipient was
// just added and now has access.
// @ts-expect-error nested navigators aren't typed -sfn
navigate('MessagesTab', {
void navigate('MessagesTab', {
screen: 'Messages',
params: {
pushToConversation: payload.convoId,
@@ -1012,7 +923,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
} else if (path) {
const [screen, params] = router.matchPath(path)
// @ts-expect-error nested navigators aren't typed -sfn
navigate('HomeTab', {screen, params})
void navigate('HomeTab', {screen, params})
notyLogger.debug(`handlePushNotificationEntry: navigate`, {
screen,
params,
@@ -1118,7 +1029,7 @@ function navigate<K extends keyof AllNavigatorParams>(
}
navigationRef.addListener('state', handler)
// @ts-ignore I dont know what would make typescript happy but I have a life -prf
// @ts-ignore I don't know what would make typescript happy but I have a life -prf
navigationRef.navigate(name, params)
}),
timeout(1e3),
@@ -1131,7 +1042,7 @@ function resetToTab(
tabName: 'HomeTab' | 'SearchTab' | 'MessagesTab' | 'NotificationsTab',
) {
if (navigationRef.isReady()) {
navigate(tabName)
void navigate(tabName)
if (navigationRef.canGoBack()) {
navigationRef.dispatch(StackActions.popToTop()) //we need to check .canGoBack() before calling it
}
+1 -1
View File
@@ -1017,7 +1017,7 @@ export type Events = {
'activityPreference:changeChannels': {
name: string
push: boolean
list: boolean
list?: boolean
}
'activityPreference:changeFilter': {
name: string
@@ -0,0 +1,105 @@
import {View} from 'react-native'
import {type AppBskyNotificationDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
import * as SettingsList from '#/screens/Settings/components/SettingsList'
import {PreferenceControls} from '#/screens/Settings/NotificationSettings/components/PreferenceControls'
import {atoms as a, useTheme, web} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env'
type NotificationSettingsDialogProps = {
control: Dialog.DialogControlProps
name: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>
syncOthers?: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>[]
icon: React.ComponentType<SVGIconProps>
titleText: React.ReactNode
subtitleText: React.ReactNode
allowDisableInApp?: boolean
}
export function NotificationSettingsDialog({
control,
name,
syncOthers,
titleText,
subtitleText,
allowDisableInApp = true,
}: NotificationSettingsDialogProps) {
return (
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
<NotificationSettingsDialogInner
control={control}
name={name}
syncOthers={syncOthers}
titleText={titleText}
subtitleText={subtitleText}
allowDisableInApp={allowDisableInApp}
/>
</Dialog.Outer>
)
}
function NotificationSettingsDialogInner({
control,
name,
syncOthers,
titleText,
subtitleText,
allowDisableInApp,
}: Omit<NotificationSettingsDialogProps, 'icon'>) {
const t = useTheme()
const {t: l} = useLingui()
const {data: preferences, isError} = useNotificationSettingsQuery()
return (
<>
<Dialog.Handle />
<Dialog.ScrollableInner
label={l`Notification settings`}
style={web({maxWidth: 400})}>
<SettingsList.Container>
<View style={[a.flex_1, a.gap_xs, a.mb_md]}>
<Text style={[a.font_semi_bold, a.text_lg]}>{titleText}</Text>
<Text
style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_snug]}>
{subtitleText}
</Text>
</View>
{isError ? (
<View style={[a.mt_md]}>
<Admonition type="error">
<Trans>Failed to load notification settings.</Trans>
</Admonition>
</View>
) : (
<PreferenceControls
name={name}
syncOthers={syncOthers}
preference={preferences?.[name]}
allowDisableInApp={allowDisableInApp}
/>
)}
</SettingsList.Container>
<Dialog.Close />
{IS_NATIVE && (
<Button
color="secondary"
size="large"
label={l`Close dialog`}
onPress={() => control.close()}
style={[a.mt_md]}>
<ButtonText>
<Trans>Done</Trans>
</ButtonText>
</Button>
)}
</Dialog.ScrollableInner>
</>
)
}
-10
View File
@@ -55,16 +55,6 @@ export type CommonNavigatorParams = {
ActivityPrivacySettings: undefined
ContentAndMediaSettings: 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
-12
View File
@@ -58,18 +58,6 @@ export const router = new Router<AllNavigatableRoutes>({
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',
FindContactsSettings: '/settings/find-contacts',
// support
Support: '/support',
+6 -5
View File
@@ -50,6 +50,7 @@ export function MessagesSettingsScreenInner({}: Props) {
did: currentAccount!.did,
})
const {preferences, setPref} = useBackgroundNotificationPreferences()
const exportCarControl = Dialog.useDialogControl()
const isGroupChatEnabled = ax.features.enabled(ax.features.GroupChatsEnable)
@@ -233,7 +234,7 @@ export function MessagesSettingsScreenInner({}: Props) {
value={preferences.playSoundChat}
style={[a.flex_row, a.align_center, a.justify_between]}
onChange={onSelectSoundSetting}>
<BellIcon style={[a.mr_2xs, t.atoms.text]} size="md" />
<BellIcon style={[a.mr_2xs, t.atoms.text]} size="lg" />
<Text
style={[
a.flex_1,
@@ -251,19 +252,19 @@ export function MessagesSettingsScreenInner({}: Props) {
)}
<View style={[a.px_xl]}>
<Toggle.Item
label={l`Export chat data`}
label={l`Export my chat data`}
name="playSoundChat"
value={preferences.playSoundChat}
style={[a.flex_row, a.align_center, a.justify_between]}
onChange={() => {
exportCarControl.open()
}}>
<CarIcon style={[a.mr_2xs, t.atoms.text]} size="md" />
<CarIcon style={[a.mr_2xs, t.atoms.text]} size="lg" />
<Text
style={[a.flex_1, a.text_md, a.font_semi_bold, t.atoms.text]}>
<Trans>Export chat data</Trans>
<Trans>Export my chat data</Trans>
</Text>
<ChevronRightIcon style={[a.ml_2xs, t.atoms.text]} size="md" />
<ChevronRightIcon style={[a.ml_2xs, t.atoms.text]} size="lg" />
</Toggle.Item>
</View>
<Divider style={{marginVertical: 10}} />
@@ -1,269 +0,0 @@
import {useCallback, useMemo} from 'react'
import {type ListRenderItemInfo, Text as RNText, View} from 'react-native'
import {type ModerationOpts} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {
type AllNavigatorParams,
type NativeStackScreenProps,
} from '#/lib/routes/types'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActivitySubscriptionsQuery} from '#/state/queries/activity-subscriptions'
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
import {List} from '#/view/com/util/List'
import {atoms as a, useTheme} from '#/alf'
import {SubscribeProfileDialog} from '#/components/activity-notifications/SubscribeProfileDialog'
import * as Admonition from '#/components/Admonition'
import {Button, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {
BellRinging_Filled_Corner0_Rounded as BellRingingFilledIcon,
BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon,
} from '#/components/icons/BellRinging'
import * as Layout from '#/components/Layout'
import {InlineLinkText} from '#/components/Link'
import {ListFooter} from '#/components/Lists'
import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
import * as SettingsList from '../components/SettingsList'
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
import {PreferenceControls} from './components/PreferenceControls'
type Props = NativeStackScreenProps<
AllNavigatorParams,
'ActivityNotificationSettings'
>
export function ActivityNotificationSettingsScreen({}: Props) {
const t = useTheme()
const {_} = useLingui()
const {data: preferences, isError} = useNotificationSettingsQuery()
const moderationOpts = useModerationOpts()
const {
data: subscriptions,
isPending,
error,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useActivitySubscriptionsQuery()
const items = useMemo(() => {
if (!subscriptions) return []
return subscriptions?.pages.flatMap(page => page.subscriptions)
}, [subscriptions])
const renderItem = useCallback(
({item}: ListRenderItemInfo<bsky.profile.AnyProfileView>) => {
if (!moderationOpts) return null
return (
<ActivitySubscriptionCard
profile={item}
moderationOpts={moderationOpts}
/>
)
},
[moderationOpts],
)
const onEndReached = useCallback(async () => {
if (isFetchingNextPage || !hasNextPage || isError) return
try {
await fetchNextPage()
} catch (err) {
logger.error('Failed to load more likes', {message: err})
}
}, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
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>
<List
ListHeaderComponent={
<SettingsList.Container>
<SettingsList.Item style={[a.align_start]}>
<SettingsList.ItemIcon icon={BellRingingIcon} />
<ItemTextWithSubtitle
bold
titleText={<Trans>Activity from others</Trans>}
subtitleText={
<Trans>
Get notified about posts and replies from accounts you
choose.
</Trans>
}
/>
</SettingsList.Item>
{isError ? (
<View style={[a.px_lg, a.pt_md]}>
<Admonition.Admonition type="error">
<Trans>Failed to load notification settings.</Trans>
</Admonition.Admonition>
</View>
) : (
<PreferenceControls
name="subscribedPost"
preference={preferences?.subscribedPost}
/>
)}
</SettingsList.Container>
}
data={items}
keyExtractor={keyExtractor}
renderItem={renderItem}
onEndReached={onEndReached}
onEndReachedThreshold={4}
ListEmptyComponent={
error ? null : (
<View style={[a.px_xl, a.py_md]}>
{!isPending ? (
<Admonition.Outer type="tip">
<Admonition.Row>
<Admonition.Icon />
<Admonition.Content>
<Admonition.Text>
<Trans>
Enable notifications for an account by visiting their
profile and pressing the{' '}
<RNText
style={[
a.font_semi_bold,
t.atoms.text_contrast_high,
]}>
bell icon
</RNText>{' '}
<BellRingingFilledIcon
size="xs"
style={t.atoms.text_contrast_high}
/>
.
</Trans>
</Admonition.Text>
<Admonition.Text>
<Trans>
If you want to restrict who can receive notifications
for your account's activity, you can change this in{' '}
<InlineLinkText
label={_(msg`Privacy and Security settings`)}
to={{screen: 'ActivityPrivacySettings'}}
style={[a.font_semi_bold]}>
Settings &rarr; Privacy and Security
</InlineLinkText>
.
</Trans>
</Admonition.Text>
</Admonition.Content>
</Admonition.Row>
</Admonition.Outer>
) : (
<View style={[a.flex_1, a.align_center, a.pt_xl]}>
<Loader size="lg" />
</View>
)}
</View>
)
}
ListFooterComponent={
<ListFooter
style={[items.length === 0 && a.border_transparent]}
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
hasNextPage={hasNextPage}
/>
}
windowSize={11}
/>
</Layout.Screen>
)
}
function keyExtractor(item: bsky.profile.AnyProfileView) {
return item.did
}
function ActivitySubscriptionCard({
profile: profileUnshadowed,
moderationOpts,
}: {
profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts
}) {
const profile = useProfileShadow(profileUnshadowed)
const control = useDialogControl()
const {_} = useLingui()
const t = useTheme()
const preview = useMemo(() => {
const actSub = profile.viewer?.activitySubscription
if (actSub?.post && actSub?.reply) {
return _(msg`Posts, Replies`)
} else if (actSub?.post) {
return _(msg`Posts`)
} else if (actSub?.reply) {
return _(msg`Replies`)
}
return _(msg`None`)
}, [_, profile.viewer?.activitySubscription])
return (
<View style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]}>
<ProfileCard.Outer>
<ProfileCard.Header>
<ProfileCard.Avatar
profile={profile}
moderationOpts={moderationOpts}
/>
<View style={[a.flex_1, a.gap_2xs]}>
<ProfileCard.NameAndHandle
profile={profile}
moderationOpts={moderationOpts}
inline
/>
<Text style={[a.leading_snug, t.atoms.text_contrast_medium]}>
{preview}
</Text>
</View>
<Button
label={_(
msg`Edit notifications from ${createSanitizedDisplayName(
profile,
)}`,
)}
size="small"
color="primary"
variant="solid"
onPress={control.open}>
<ButtonText>
<Trans>Edit</Trans>
</ButtonText>
</Button>
</ProfileCard.Header>
</ProfileCard.Outer>
<SubscribeProfileDialog
control={control}
profile={profile}
moderationOpts={moderationOpts}
includeProfile
/>
</View>
)
}
@@ -1,60 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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>
)
}
@@ -1,65 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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 of 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>
)
}
@@ -1,63 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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>
)
}
@@ -1,68 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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>
)
}
@@ -1,63 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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>
)
}
@@ -1,60 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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>
)
}
@@ -1,62 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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} />
)}
</SettingsList.Container>
</Layout.Content>
</Layout.Screen>
)
}
@@ -1,63 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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>
)
}
@@ -1,66 +0,0 @@
import {View} from 'react-native'
import {Trans} from '@lingui/react/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>
)
}
@@ -1,9 +1,7 @@
import {useMemo} from 'react'
import {View} from 'react-native'
import {type AppBskyNotificationDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNotificationSettingsUpdateMutation} from '#/state/queries/notifications/settings'
import {atoms as a, platform, useTheme} from '#/alf'
@@ -28,6 +26,7 @@ export function PreferenceControls({
preference?:
| AppBskyNotificationDefs.Preference
| AppBskyNotificationDefs.FilterablePreference
| AppBskyNotificationDefs.ChatPreference
allowDisableInApp?: boolean
}) {
if (!preference)
@@ -58,16 +57,17 @@ export function Inner({
preference:
| AppBskyNotificationDefs.Preference
| AppBskyNotificationDefs.FilterablePreference
| AppBskyNotificationDefs.ChatPreference
allowDisableInApp: boolean
}) {
const t = useTheme()
const {_} = useLingui()
const {t: l} = useLingui()
const ax = useAnalytics()
const {mutate} = useNotificationSettingsUpdateMutation()
const channels = useMemo(() => {
const arr = []
if (preference.list) arr.push('list')
if ('list' in preference && preference.list) arr.push('list')
if (preference.push) arr.push('push')
return arr
}, [preference])
@@ -75,15 +75,18 @@ export function Inner({
const onChangeChannels = (change: string[]) => {
const newPreference = {
...preference,
list: change.includes('list'),
...('list' in preference ? {list: change.includes('list')} : {}),
push: change.includes('push'),
} satisfies typeof preference
} as typeof preference
ax.metric('activityPreference:changeChannels', {
const metrics: {name: string; push: boolean; list?: boolean} = {
name,
push: newPreference.push,
list: newPreference.list,
})
}
if ('list' in newPreference) {
metrics.list = newPreference.list
}
ax.metric('activityPreference:changeChannels', metrics)
mutate({
[name]: newPreference,
@@ -92,7 +95,7 @@ export function Inner({
}
const onChangeFilter = ([change]: string[]) => {
if (change !== 'all' && change !== 'follows')
if (change !== 'all' && change !== 'follows' && change !== 'accepted')
throw new Error('Invalid filter')
const newPreference = {
@@ -109,15 +112,15 @@ export function Inner({
}
return (
<View style={[a.px_xl, a.pt_md, a.gap_sm]}>
<View style={[a.gap_sm]}>
<Toggle.Group
type="checkbox"
label={_(msg`Select your preferred notification channels`)}
label={l`Select your preferred notification channels`}
values={channels}
onChange={onChangeChannels}>
<View style={[a.gap_sm]}>
<Toggle.Item
label={_(msg`Receive push notifications`)}
label={l`Receive push notifications`}
name="push"
style={[
a.py_xs,
@@ -134,7 +137,7 @@ export function Inner({
</Toggle.Item>
{allowDisableInApp && (
<Toggle.Item
label={_(msg`Receive in-app notifications`)}
label={l`Receive in-app notifications`}
name="list"
style={[
a.py_xs,
@@ -160,39 +163,44 @@ export function Inner({
</Text>
<Toggle.Group
type="radio"
label={_(msg`Filter who you receive notifications from`)}
label={l`Filter who you receive notifications from`}
values={[preference.include]}
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, a.gap_sm]}>
<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, a.gap_sm]}>
<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 highlightRow label={l`Everyone`} name="all">
{({selected}) => (
<Toggle.RadioWithLabel
label={l`Everyone`}
selected={selected}
/>
)}
</Toggle.Item>
{name === 'chat' ? (
<Toggle.Item
highlightRow
label={l`Accepted conversations`}
name="accepted">
{({selected}) => (
<Toggle.RadioWithLabel
label={l`Accepted conversations`}
selected={selected}
/>
)}
</Toggle.Item>
) : (
<Toggle.Item
highlightRow
label={l`People I follow`}
name="follows">
{({selected}) => (
<Toggle.RadioWithLabel
label={l`People I follow`}
selected={selected}
/>
)}
</Toggle.Item>
)}
</View>
</Toggle.Group>
</>
@@ -2,9 +2,7 @@ import {useEffect} from 'react'
import {Linking, View} from 'react-native'
import * as Notification from 'expo-notifications'
import {type AppBskyNotificationDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {useQuery, useQueryClient} from '@tanstack/react-query'
import {useAppState} from '#/lib/appState'
@@ -15,6 +13,8 @@ import {
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
import {atoms as a} from '#/alf'
import {Admonition} from '#/components/Admonition'
import * as Dialog from '#/components/Dialog'
import {NotificationSettingsDialog} from '#/components/dialogs/NotificationSettingsDialog'
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'
@@ -39,10 +39,21 @@ const RQKEY = ['notification-permissions']
type Props = NativeStackScreenProps<AllNavigatorParams, 'NotificationSettings'>
export function NotificationSettingsScreen({}: Props) {
const {_} = useLingui()
const {t: l} = useLingui()
const queryClient = useQueryClient()
const {data: settings, isError} = useNotificationSettingsQuery()
const likeDialogControl = Dialog.useDialogControl()
const followDialogControl = Dialog.useDialogControl()
const replyDialogControl = Dialog.useDialogControl()
const mentionDialogControl = Dialog.useDialogControl()
const quoteDialogControl = Dialog.useDialogControl()
const repostDialogControl = Dialog.useDialogControl()
const activityDialogControl = Dialog.useDialogControl()
const likeRepostDialogControl = Dialog.useDialogControl()
const repostRepostDialogControl = Dialog.useDialogControl()
const miscDialogControl = Dialog.useDialogControl()
const {data: permissions, refetch} = useQuery({
queryKey: RQKEY,
queryFn: async () => {
@@ -54,7 +65,7 @@ export function NotificationSettingsScreen({}: Props) {
const appState = useAppState()
useEffect(() => {
if (appState === 'active') {
refetch()
void refetch()
}
}, [appState, refetch])
@@ -76,10 +87,10 @@ export function NotificationSettingsScreen({}: Props) {
],
)
} catch {
Linking.openSettings()
void Linking.openSettings()
}
} else if (IS_IOS) {
Linking.openSettings()
void Linking.openSettings()
}
}
}
@@ -100,8 +111,8 @@ export function NotificationSettingsScreen({}: Props) {
{permissions && !permissions.granted && (
<>
<SettingsList.PressableItem
label={_(msg`Enable push notifications`)}
onPress={onRequestPermissions}>
label={l`Enable push notifications`}
onPress={() => void onRequestPermissions()}>
<SettingsList.ItemIcon icon={HapticIcon} />
<SettingsList.ItemText>
<Trans>Enable push notifications</Trans>
@@ -118,9 +129,9 @@ export function NotificationSettingsScreen({}: Props) {
</View>
)}
<View style={[a.gap_sm]}>
<SettingsList.LinkItem
label={_(msg`Settings for like notifications`)}
to={{screen: 'LikeNotificationSettings'}}
<SettingsList.PressableItem
label={l`Settings for like notifications`}
onPress={likeDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={HeartIcon} />
<ItemTextWithSubtitle
@@ -128,10 +139,10 @@ export function NotificationSettingsScreen({}: Props) {
subtitleText={<SettingPreview preference={settings?.like} />}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(msg`Settings for new follower notifications`)}
to={{screen: 'NewFollowerNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for new follower notifications`}
onPress={followDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={PersonPlusIcon} />
<ItemTextWithSubtitle
@@ -139,10 +150,10 @@ export function NotificationSettingsScreen({}: Props) {
subtitleText={<SettingPreview preference={settings?.follow} />}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(msg`Settings for reply notifications`)}
to={{screen: 'ReplyNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for reply notifications`}
onPress={replyDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={BubbleIcon} />
<ItemTextWithSubtitle
@@ -150,10 +161,10 @@ export function NotificationSettingsScreen({}: Props) {
subtitleText={<SettingPreview preference={settings?.reply} />}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(msg`Settings for mention notifications`)}
to={{screen: 'MentionNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for mention notifications`}
onPress={mentionDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={AtIcon} />
<ItemTextWithSubtitle
@@ -161,10 +172,10 @@ export function NotificationSettingsScreen({}: Props) {
subtitleText={<SettingPreview preference={settings?.mention} />}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(msg`Settings for quote notifications`)}
to={{screen: 'QuoteNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for quote notifications`}
onPress={quoteDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={CloseQuoteIcon} />
<ItemTextWithSubtitle
@@ -172,10 +183,10 @@ export function NotificationSettingsScreen({}: Props) {
subtitleText={<SettingPreview preference={settings?.quote} />}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(msg`Settings for repost notifications`)}
to={{screen: 'RepostNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for repost notifications`}
onPress={repostDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={RepostIcon} />
<ItemTextWithSubtitle
@@ -183,10 +194,10 @@ export function NotificationSettingsScreen({}: Props) {
subtitleText={<SettingPreview preference={settings?.repost} />}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(msg`Settings for activity from others`)}
to={{screen: 'ActivityNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for activity from others`}
onPress={activityDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={BellRingingIcon} />
<ItemTextWithSubtitle
@@ -196,12 +207,10 @@ export function NotificationSettingsScreen({}: Props) {
}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(
msg`Settings for notifications for likes of your reposts`,
)}
to={{screen: 'LikesOnRepostsNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for notifications for likes of your reposts`}
onPress={likeRepostDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={LikeRepostIcon} />
<ItemTextWithSubtitle
@@ -211,12 +220,10 @@ export function NotificationSettingsScreen({}: Props) {
}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(
msg`Settings for notifications for reposts of your reposts`,
)}
to={{screen: 'RepostsOnRepostsNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for notifications for reposts of your reposts`}
onPress={repostRepostDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={RepostRepostIcon} />
<ItemTextWithSubtitle
@@ -226,10 +233,10 @@ export function NotificationSettingsScreen({}: Props) {
}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
<SettingsList.LinkItem
label={_(msg`Settings for notifications for everything else`)}
to={{screen: 'MiscellaneousNotificationSettings'}}
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for notifications for everything else`}
onPress={miscDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={ShapesIcon} />
<ItemTextWithSubtitle
@@ -241,10 +248,105 @@ export function NotificationSettingsScreen({}: Props) {
}
showSkeleton={!settings}
/>
</SettingsList.LinkItem>
</SettingsList.PressableItem>
</View>
</SettingsList.Container>
</Layout.Content>
<NotificationSettingsDialog
control={likeDialogControl}
name="like"
icon={HeartIcon}
titleText={<Trans>Likes</Trans>}
subtitleText={
<Trans>Get notifications when people like your posts.</Trans>
}
/>
<NotificationSettingsDialog
control={followDialogControl}
name="follow"
icon={PersonPlusIcon}
titleText={<Trans>New followers</Trans>}
subtitleText={<Trans>Get notifications when people follow you.</Trans>}
/>
<NotificationSettingsDialog
control={replyDialogControl}
name="reply"
icon={BubbleIcon}
titleText={<Trans>Replies</Trans>}
subtitleText={
<Trans>Get notifications when people reply to your posts.</Trans>
}
/>
<NotificationSettingsDialog
control={mentionDialogControl}
name="mention"
icon={AtIcon}
titleText={<Trans>Mentions</Trans>}
subtitleText={<Trans>Get notifications when people mention you.</Trans>}
/>
<NotificationSettingsDialog
control={quoteDialogControl}
name="quote"
icon={CloseQuoteIcon}
titleText={<Trans>Quotes</Trans>}
subtitleText={
<Trans>Get notifications when people quote your posts.</Trans>
}
/>
<NotificationSettingsDialog
control={repostDialogControl}
name="repost"
icon={RepostIcon}
titleText={<Trans>Reposts</Trans>}
subtitleText={
<Trans>Get notifications when people repost your posts.</Trans>
}
/>
<NotificationSettingsDialog
control={activityDialogControl}
name="subscribedPost"
icon={BellRingingIcon}
titleText={<Trans>Activity from others</Trans>}
subtitleText={
<Trans>
Get notifications when there's activity on posts you're subscribed
to.
</Trans>
}
allowDisableInApp={false}
/>
<NotificationSettingsDialog
control={likeRepostDialogControl}
name="likeViaRepost"
icon={LikeRepostIcon}
titleText={<Trans>Likes of your reposts</Trans>}
subtitleText={
<Trans>Get notifications when people like your reposts.</Trans>
}
/>
<NotificationSettingsDialog
control={repostRepostDialogControl}
name="repostViaRepost"
icon={RepostRepostIcon}
titleText={<Trans>Reposts of your reposts</Trans>}
subtitleText={
<Trans>Get notifications when people repost your reposts.</Trans>
}
/>
<NotificationSettingsDialog
control={miscDialogControl}
name="starterpackJoined"
syncOthers={['verified', 'unverified']}
icon={ShapesIcon}
titleText={<Trans>Everything else</Trans>}
subtitleText={
<Trans>
Get notifications for starter pack joins, verification, and other
activity.
</Trans>
}
allowDisableInApp={false}
/>
</Layout.Screen>
)
}
@@ -256,38 +358,38 @@ function SettingPreview({
| AppBskyNotificationDefs.Preference
| AppBskyNotificationDefs.FilterablePreference
}) {
const {_} = useLingui()
const {t: l} = useLingui()
if (!preference) {
return null
} else {
if ('include' in preference) {
if (preference.include === 'all') {
if (preference.list && preference.push) {
return _(msg`In-app, Push, Everyone`)
return l`In-app, push, everyone`
} else if (preference.list) {
return _(msg`In-app, Everyone`)
return l`In-app, everyone`
} else if (preference.push) {
return _(msg`Push, Everyone`)
return l`Push, everyone`
}
} else if (preference.include === 'follows') {
if (preference.list && preference.push) {
return _(msg`In-app, Push, People you follow`)
return l`In-app, push, people you follow`
} else if (preference.list) {
return _(msg`In-app, People you follow`)
return l`In-app, people you follow`
} else if (preference.push) {
return _(msg`Push, People you follow`)
return l`Push, people you follow`
}
}
} else {
if (preference.list && preference.push) {
return _(msg`In-app, Push`)
return l`In-app, push`
} else if (preference.list) {
return _(msg`In-app`)
return l`In-app`
} else if (preference.push) {
return _(msg`Push`)
return l`Push`
}
}
}
return _(msg`Off`)
return l`Off`
}
@@ -115,12 +115,12 @@ export function ExportCarDialog({
<Button
color="primary"
size="large"
label={l`Download CAR file`}
label={l`Download profile data`}
disabled={!!loading}
onPress={() => void download()}>
<ButtonIcon icon={loading === 'repo' ? Loader : DownloadIcon} />
<ButtonText>
<Trans context="button">Download CAR file</Trans>
<Trans context="button">Download profile data</Trans>
</ButtonText>
</Button>
+1 -1
View File
@@ -45,7 +45,7 @@ export function useNotificationSettingsUpdateMutation() {
},
onError: e => {
logger.error('Could not update notification settings', {message: e})
queryClient.invalidateQueries({queryKey: RQKEY})
void queryClient.invalidateQueries({queryKey: RQKEY})
Toast.show(t`Could not update notification settings`, {
type: 'error',
})