Granular notification settings (#8484)
* add mockup screen * add notification index screen * add redirect screen * upgrade sdk * new icons * add new screens * make router typesafe, finish adding screens * add routes to go server * load settings * push notif settings * improve web * fix lockfile lint * no $type on preferences * prompt to enable push notifications * fix reply prefs * space out options * fix copy error * Update RepostsOnRepostsNotificationSettings.tsx * only send minimal diff to putPrefs * fix yarn.lock * Update Navigation.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/screens/Settings/NotificationSettings/index.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * add description to `syncOthers` --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
This commit is contained in:
@@ -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'
|
||||
|
||||
+4
-1
@@ -2,9 +2,11 @@ import {useMemo, useState} from 'react'
|
||||
import {type TextStyle, View, type ViewStyle} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import {type CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {
|
||||
preferencesQueryKey,
|
||||
usePreferencesQuery,
|
||||
@@ -24,7 +26,8 @@ import * as Layout from '#/components/Layout'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function SettingsInterests() {
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'InterestsSettings'>
|
||||
export function InterestsSettingsScreen({}: Props) {
|
||||
const t = useTheme()
|
||||
const gutters = useGutters(['base'])
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import {Text} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {AllNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
|
||||
import {useNotificationSettingsMutation} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Error} from '#/components/Error'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as SettingsList from './components/SettingsList'
|
||||
|
||||
type Props = NativeStackScreenProps<AllNavigatorParams, 'NotificationSettings'>
|
||||
export function NotificationSettingsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
|
||||
const {
|
||||
data,
|
||||
isError: isQueryError,
|
||||
refetch,
|
||||
} = useNotificationFeedQuery({
|
||||
filter: 'all',
|
||||
})
|
||||
const serverPriority = data?.pages.at(0)?.priority
|
||||
|
||||
const {
|
||||
mutate: onChangePriority,
|
||||
isPending: isMutationPending,
|
||||
variables,
|
||||
} = useNotificationSettingsMutation()
|
||||
|
||||
const priority = isMutationPending
|
||||
? variables[0] === 'enabled'
|
||||
: serverPriority
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notification Settings</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
{isQueryError ? (
|
||||
<Error
|
||||
title={_(msg`Oops!`)}
|
||||
message={_(msg`Something went wrong!`)}
|
||||
onRetry={refetch}
|
||||
sideBorders={false}
|
||||
/>
|
||||
) : (
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Group>
|
||||
<SettingsList.ItemIcon icon={BeakerIcon} />
|
||||
<SettingsList.ItemText>
|
||||
<Trans>Notification filters</Trans>
|
||||
</SettingsList.ItemText>
|
||||
<Toggle.Group
|
||||
label={_(msg`Priority notifications`)}
|
||||
type="checkbox"
|
||||
values={priority ? ['enabled'] : []}
|
||||
onChange={onChangePriority}
|
||||
disabled={typeof priority !== 'boolean' || isMutationPending}>
|
||||
<Toggle.Item
|
||||
name="enabled"
|
||||
label={_(msg`Enable priority notifications`)}
|
||||
style={[a.flex_1, a.justify_between]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Enable priority notifications</Trans>
|
||||
</Toggle.LabelText>
|
||||
{!data ? <Loader size="md" /> : <Toggle.Platform />}
|
||||
</Toggle.Item>
|
||||
</Toggle.Group>
|
||||
</SettingsList.Group>
|
||||
<SettingsList.Item>
|
||||
<Admonition type="warning" style={[a.flex_1]}>
|
||||
<Trans>
|
||||
<Text style={[a.font_bold]}>Experimental:</Text> When this
|
||||
preference is enabled, you'll only receive reply and quote
|
||||
notifications from users you follow. We'll continue to add
|
||||
more controls here over time.
|
||||
</Trans>
|
||||
</Admonition>
|
||||
</SettingsList.Item>
|
||||
</SettingsList.Container>
|
||||
)}
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Heart2_Stroke2_Corner0_Rounded as HeartIcon} from '#/components/icons/Heart2'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'LikeNotificationSettings'
|
||||
>
|
||||
export function LikeNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={HeartIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>Likes</Trans>}
|
||||
subtitleText={
|
||||
<Trans>Get notifications when people like your posts.</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls name="like" preference={preferences?.like} />
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {LikeRepost_Stroke2_Corner2_Rounded as LikeRepostIcon} from '#/components/icons/Heart2'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'LikesOnRepostsNotificationSettings'
|
||||
>
|
||||
export function LikesOnRepostsNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={LikeRepostIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>Likes on your reposts</Trans>}
|
||||
subtitleText={
|
||||
<Trans>
|
||||
Get notifications when people like posts that you've reposted.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls
|
||||
name="likeViaRepost"
|
||||
preference={preferences?.likeViaRepost}
|
||||
/>
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'MentionNotificationSettings'
|
||||
>
|
||||
export function MentionNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={AtIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>Mentions</Trans>}
|
||||
subtitleText={
|
||||
<Trans>Get notifications when people mention you.</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls
|
||||
name="mention"
|
||||
preference={preferences?.mention}
|
||||
/>
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'MiscellaneousNotificationSettings'
|
||||
>
|
||||
export function MiscellaneousNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={ShapesIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>Everything else</Trans>}
|
||||
subtitleText={
|
||||
<Trans>
|
||||
Notifications for everything else, such as when someone joins
|
||||
via one of your starter packs.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls
|
||||
name="starterpackJoined"
|
||||
preference={preferences?.starterpackJoined}
|
||||
syncOthers={['verified', 'unverified']}
|
||||
allowDisableInApp={false}
|
||||
/>
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {PersonPlus_Stroke2_Corner2_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'NewFollowerNotificationSettings'
|
||||
>
|
||||
export function NewFollowerNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={PersonPlusIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>New followers</Trans>}
|
||||
subtitleText={
|
||||
<Trans>Get notifications when people follow you.</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls
|
||||
name="follow"
|
||||
preference={preferences?.follow}
|
||||
/>
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {CloseQuote_Stroke2_Corner0_Rounded as CloseQuoteIcon} from '#/components/icons/Quote'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'QuoteNotificationSettings'
|
||||
>
|
||||
export function QuoteNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={CloseQuoteIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>Quotes</Trans>}
|
||||
subtitleText={
|
||||
<Trans>Get notifications when people quote your posts.</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls name="quote" preference={preferences?.quote} />
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'ReplyNotificationSettings'
|
||||
>
|
||||
export function ReplyNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={BubbleIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>Replies</Trans>}
|
||||
subtitleText={
|
||||
<Trans>
|
||||
Get notifications when people reply to your posts.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls
|
||||
name="reply"
|
||||
preference={preferences?.reply}
|
||||
allowDisableInApp={false}
|
||||
/>
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'RepostNotificationSettings'
|
||||
>
|
||||
export function RepostNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={RepostIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>Reposts</Trans>}
|
||||
subtitleText={
|
||||
<Trans>Get notifications when people repost your posts.</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls
|
||||
name="repost"
|
||||
preference={preferences?.repost}
|
||||
/>
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {RepostRepost_Stroke2_Corner2_Rounded as RepostRepostIcon} from '#/components/icons/Repost'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
import {PreferenceControls} from './components/PreferenceControls'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'RepostsOnRepostsNotificationSettings'
|
||||
>
|
||||
export function RepostsOnRepostsNotificationSettingsScreen({}: Props) {
|
||||
const {data: preferences, isError} = useNotificationSettingsQuery()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
<SettingsList.Item style={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={RepostRepostIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
bold
|
||||
titleText={<Trans>Reposts of your reposts</Trans>}
|
||||
subtitleText={
|
||||
<Trans>
|
||||
Get notifications when people repost posts that you've
|
||||
reposted.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
</SettingsList.Item>
|
||||
{isError ? (
|
||||
<View style={[a.px_lg, a.pt_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
) : (
|
||||
<PreferenceControls
|
||||
name="repostViaRepost"
|
||||
preference={preferences?.repostViaRepost}
|
||||
/>
|
||||
)}
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import * as Skele from '#/components/Skeleton'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as SettingsList from '../../components/SettingsList'
|
||||
|
||||
export function ItemTextWithSubtitle({
|
||||
titleText,
|
||||
subtitleText,
|
||||
bold = false,
|
||||
showSkeleton = false,
|
||||
}: {
|
||||
titleText: React.ReactNode
|
||||
subtitleText: React.ReactNode
|
||||
bold?: boolean
|
||||
showSkeleton?: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View style={[a.flex_1, bold ? a.gap_xs : a.gap_2xs]}>
|
||||
<SettingsList.ItemText style={bold && [a.font_bold, a.text_lg]}>
|
||||
{titleText}
|
||||
</SettingsList.ItemText>
|
||||
{showSkeleton ? (
|
||||
<Skele.Text style={[a.text_sm, {width: 120}]} />
|
||||
) : (
|
||||
<Text style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_snug]}>
|
||||
{subtitleText}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
import {useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {type AppBskyNotificationDefs} from '@atproto/api'
|
||||
import {type FilterablePreference} from '@atproto/api/dist/client/types/app/bsky/notification/defs'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useNotificationSettingsUpdateMutation} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a, platform, useTheme} from '#/alf'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {Divider} from '../../components/SettingsList'
|
||||
|
||||
export function PreferenceControls({
|
||||
name,
|
||||
syncOthers,
|
||||
preference,
|
||||
allowDisableInApp = true,
|
||||
}: {
|
||||
name: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>
|
||||
/**
|
||||
* Keep other prefs in sync with `name`. For use in the "everything else" category
|
||||
* which groups starterpack joins + verified + unverified notifications into a single toggle.
|
||||
*/
|
||||
syncOthers?: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>[]
|
||||
preference?: AppBskyNotificationDefs.Preference | FilterablePreference
|
||||
allowDisableInApp?: boolean
|
||||
}) {
|
||||
if (!preference)
|
||||
return (
|
||||
<View style={[a.w_full, a.pt_5xl, a.align_center]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
)
|
||||
|
||||
return (
|
||||
<Inner
|
||||
name={name}
|
||||
syncOthers={syncOthers}
|
||||
preference={preference}
|
||||
allowDisableInApp={allowDisableInApp}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Inner({
|
||||
name,
|
||||
syncOthers = [],
|
||||
preference,
|
||||
allowDisableInApp,
|
||||
}: {
|
||||
name: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>
|
||||
syncOthers?: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>[]
|
||||
preference: AppBskyNotificationDefs.Preference | FilterablePreference
|
||||
allowDisableInApp: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {mutate} = useNotificationSettingsUpdateMutation()
|
||||
|
||||
const channels = useMemo(() => {
|
||||
const arr = []
|
||||
if (preference.list) arr.push('list')
|
||||
if (preference.push) arr.push('push')
|
||||
return arr
|
||||
}, [preference])
|
||||
|
||||
const onChangeChannels = (change: string[]) => {
|
||||
const newPreference = {
|
||||
...preference,
|
||||
list: change.includes('list'),
|
||||
push: change.includes('push'),
|
||||
} satisfies typeof preference
|
||||
|
||||
mutate({
|
||||
[name]: newPreference,
|
||||
...Object.fromEntries(syncOthers.map(key => [key, newPreference])),
|
||||
})
|
||||
}
|
||||
|
||||
const onChangeFilter = ([change]: string[]) => {
|
||||
if (change !== 'all' && change !== 'follows')
|
||||
throw new Error('Invalid filter')
|
||||
|
||||
const newPreference = {
|
||||
...preference,
|
||||
filter: change,
|
||||
} satisfies typeof preference
|
||||
|
||||
mutate({
|
||||
[name]: newPreference,
|
||||
...Object.fromEntries(syncOthers.map(key => [key, newPreference])),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[a.px_xl, a.pt_md, a.gap_sm]}>
|
||||
<Toggle.Group
|
||||
type="checkbox"
|
||||
label={_(`Select your preferred notification channels`)}
|
||||
values={channels}
|
||||
onChange={onChangeChannels}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Toggle.Item
|
||||
label={_(msg`Receive push notifications`)}
|
||||
name="push"
|
||||
style={[
|
||||
a.py_xs,
|
||||
platform({
|
||||
native: [a.justify_between],
|
||||
web: [a.flex_row_reverse, a.gap_md],
|
||||
}),
|
||||
]}>
|
||||
<Toggle.LabelText
|
||||
style={[t.atoms.text, a.font_normal, a.text_md, a.flex_1]}>
|
||||
<Trans>Push notifications</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Platform />
|
||||
</Toggle.Item>
|
||||
{allowDisableInApp && (
|
||||
<Toggle.Item
|
||||
label={_(msg`Receive in-app notifications`)}
|
||||
name="list"
|
||||
style={[
|
||||
a.py_xs,
|
||||
platform({
|
||||
native: [a.justify_between],
|
||||
web: [a.flex_row_reverse, a.gap_md],
|
||||
}),
|
||||
]}>
|
||||
<Toggle.LabelText
|
||||
style={[t.atoms.text, a.font_normal, a.text_md, a.flex_1]}>
|
||||
<Trans>In-app notifications</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Platform />
|
||||
</Toggle.Item>
|
||||
)}
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
{'filter' in preference && (
|
||||
<>
|
||||
<Divider />
|
||||
<Text style={[a.font_bold, a.text_md]}>From</Text>
|
||||
<Toggle.Group
|
||||
type="radio"
|
||||
label={_('Filter who you receive notifications from')}
|
||||
values={[preference.filter]}
|
||||
onChange={onChangeFilter}
|
||||
disabled={channels.length === 0}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Toggle.Item
|
||||
label={_(msg`Everyone`)}
|
||||
name="all"
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.py_xs,
|
||||
platform({native: [a.gap_sm], web: [a.gap_md]}),
|
||||
]}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText
|
||||
style={[
|
||||
channels.length > 0 && t.atoms.text,
|
||||
a.font_normal,
|
||||
a.text_md,
|
||||
]}>
|
||||
<Trans>Everyone</Trans>
|
||||
</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
label={_(msg`People I follow`)}
|
||||
name="follows"
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.py_xs,
|
||||
platform({native: [a.gap_sm], web: [a.gap_md]}),
|
||||
]}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText
|
||||
style={[
|
||||
channels.length > 0 && t.atoms.text,
|
||||
a.font_normal,
|
||||
a.text_md,
|
||||
]}>
|
||||
<Trans>People I follow</Trans>
|
||||
</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
import {useEffect} from 'react'
|
||||
import {Linking, View} from 'react-native'
|
||||
import * as Notification from 'expo-notifications'
|
||||
import {type AppBskyNotificationDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useAppState} from '#/lib/hooks/useAppState'
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {isAndroid, isIOS, isWeb} from '#/platform/detection'
|
||||
import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At'
|
||||
// import {BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging'
|
||||
import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble'
|
||||
import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic'
|
||||
import {
|
||||
Heart2_Stroke2_Corner0_Rounded as HeartIcon,
|
||||
LikeRepost_Stroke2_Corner2_Rounded as LikeRepostIcon,
|
||||
} from '#/components/icons/Heart2'
|
||||
import {PersonPlus_Stroke2_Corner2_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||
import {CloseQuote_Stroke2_Corner0_Rounded as CloseQuoteIcon} from '#/components/icons/Quote'
|
||||
import {
|
||||
Repost_Stroke2_Corner2_Rounded as RepostIcon,
|
||||
RepostRepost_Stroke2_Corner2_Rounded as RepostRepostIcon,
|
||||
} from '#/components/icons/Repost'
|
||||
import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as SettingsList from '../components/SettingsList'
|
||||
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
|
||||
|
||||
const RQKEY = ['notification-permissions']
|
||||
|
||||
type Props = NativeStackScreenProps<AllNavigatorParams, 'NotificationSettings'>
|
||||
export function NotificationSettingsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const queryClient = useQueryClient()
|
||||
const {data: settings, isError} = useNotificationSettingsQuery()
|
||||
|
||||
const {data: permissions, refetch} = useQuery({
|
||||
queryKey: RQKEY,
|
||||
queryFn: async () => {
|
||||
if (isWeb) return null
|
||||
return await Notification.getPermissionsAsync()
|
||||
},
|
||||
})
|
||||
|
||||
const appState = useAppState()
|
||||
useEffect(() => {
|
||||
if (appState === 'active') {
|
||||
refetch()
|
||||
}
|
||||
}, [appState, refetch])
|
||||
|
||||
const onRequestPermissions = async () => {
|
||||
if (isWeb) return
|
||||
if (permissions?.canAskAgain) {
|
||||
const response = await Notification.requestPermissionsAsync()
|
||||
queryClient.setQueryData(RQKEY, response)
|
||||
} else {
|
||||
if (isAndroid) {
|
||||
try {
|
||||
await Linking.sendIntent(
|
||||
'android.settings.APP_NOTIFICATION_SETTINGS',
|
||||
[
|
||||
{
|
||||
key: 'android.provider.extra.APP_PACKAGE',
|
||||
value: 'xyz.blueskyweb.app',
|
||||
},
|
||||
],
|
||||
)
|
||||
} catch {
|
||||
Linking.openSettings()
|
||||
}
|
||||
} else if (isIOS) {
|
||||
Linking.openSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content>
|
||||
<SettingsList.Container>
|
||||
{permissions && !permissions.granted && (
|
||||
<>
|
||||
<SettingsList.PressableItem
|
||||
label={_(msg`Enable push notifications`)}
|
||||
onPress={onRequestPermissions}>
|
||||
<SettingsList.ItemIcon icon={HapticIcon} />
|
||||
<SettingsList.ItemText>
|
||||
<Trans>Enable push notifications</Trans>
|
||||
</SettingsList.ItemText>
|
||||
</SettingsList.PressableItem>
|
||||
<SettingsList.Divider />
|
||||
</>
|
||||
)}
|
||||
{isError && (
|
||||
<View style={[a.px_lg, a.pb_md]}>
|
||||
<Admonition type="error">
|
||||
<Trans>Failed to load notification settings.</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
)}
|
||||
<View style={[a.gap_sm]}>
|
||||
<SettingsList.LinkItem
|
||||
label={_(msg`Settings for reply notifications`)}
|
||||
to={{screen: 'ReplyNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={BubbleIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Replies</Trans>}
|
||||
subtitleText={<SettingPreview preference={settings?.reply} />}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
label={_(msg`Settings for mention notifications`)}
|
||||
to={{screen: 'MentionNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={AtIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Mentions</Trans>}
|
||||
subtitleText={<SettingPreview preference={settings?.mention} />}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
label={_(msg`Settings for quote notifications`)}
|
||||
to={{screen: 'QuoteNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={CloseQuoteIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Quotes</Trans>}
|
||||
subtitleText={<SettingPreview preference={settings?.quote} />}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
label={_(msg`Settings for like notifications`)}
|
||||
to={{screen: 'LikeNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={HeartIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Likes</Trans>}
|
||||
subtitleText={<SettingPreview preference={settings?.like} />}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
label={_(msg`Settings for repost notifications`)}
|
||||
to={{screen: 'RepostNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={RepostIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Reposts</Trans>}
|
||||
subtitleText={<SettingPreview preference={settings?.repost} />}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
label={_(msg`Settings for new follower notifications`)}
|
||||
to={{screen: 'NewFollowerNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={PersonPlusIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>New followers</Trans>}
|
||||
subtitleText={<SettingPreview preference={settings?.follow} />}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
{/* <SettingsList.LinkItem
|
||||
label={_(msg`Settings for activity alerts`)}
|
||||
to={{screen: 'ActivityNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={BellRingingIcon} />
|
||||
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Activity alerts</Trans>}
|
||||
subtitleText={
|
||||
<SettingPreview preference={settings?.subscribedPost} />
|
||||
}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem> */}
|
||||
<SettingsList.LinkItem
|
||||
label={_(
|
||||
msg`Settings for notifications for likes on your reposts`,
|
||||
)}
|
||||
to={{screen: 'LikesOnRepostsNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={LikeRepostIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Likes on your reposts</Trans>}
|
||||
subtitleText={
|
||||
<SettingPreview preference={settings?.likeViaRepost} />
|
||||
}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
label={_(
|
||||
msg`Settings for notifications for reposts of your reposts`,
|
||||
)}
|
||||
to={{screen: 'RepostsOnRepostsNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={RepostRepostIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Reposts of your reposts</Trans>}
|
||||
subtitleText={
|
||||
<SettingPreview preference={settings?.repostViaRepost} />
|
||||
}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
label={_(msg`Settings for notifications for everything else`)}
|
||||
to={{screen: 'MiscellaneousNotificationSettings'}}
|
||||
contentContainerStyle={[a.align_start]}>
|
||||
<SettingsList.ItemIcon icon={ShapesIcon} />
|
||||
<ItemTextWithSubtitle
|
||||
titleText={<Trans>Everything else</Trans>}
|
||||
// technically a bundle of several settings, but since they're set together
|
||||
// and are most likely in sync we'll just show the state of one of them
|
||||
subtitleText={
|
||||
<SettingPreview preference={settings?.starterpackJoined} />
|
||||
}
|
||||
showSkeleton={!settings}
|
||||
/>
|
||||
</SettingsList.LinkItem>
|
||||
</View>
|
||||
</SettingsList.Container>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingPreview({
|
||||
preference,
|
||||
}: {
|
||||
preference?:
|
||||
| AppBskyNotificationDefs.Preference
|
||||
| AppBskyNotificationDefs.FilterablePreference
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
if (!preference) {
|
||||
return null
|
||||
} else {
|
||||
if ('filter' in preference) {
|
||||
if (preference.filter === 'all') {
|
||||
if (preference.list && preference.push) {
|
||||
return _(msg`In-app, Push, Everyone`)
|
||||
} else if (preference.list) {
|
||||
return _(msg`In-app, Everyone`)
|
||||
} else if (preference.push) {
|
||||
return _(msg`Push, Everyone`)
|
||||
}
|
||||
} else if (preference.filter === 'follows') {
|
||||
if (preference.list && preference.push) {
|
||||
return _(msg`In-app, Push, People you follow`)
|
||||
} else if (preference.list) {
|
||||
return _(msg`In-app, People you follow`)
|
||||
} else if (preference.push) {
|
||||
return _(msg`Push, People you follow`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (preference.list && preference.push) {
|
||||
return _(msg`In-app, Push`)
|
||||
} else if (preference.list) {
|
||||
return _(msg`In-app`)
|
||||
} else if (preference.push) {
|
||||
return _(msg`Push`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _(msg`Off`)
|
||||
}
|
||||
@@ -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) {
|
||||
<Trans>Moderation</Trans>
|
||||
</SettingsList.ItemText>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
to="/settings/notifications"
|
||||
label={_(msg`Notifications`)}>
|
||||
<SettingsList.ItemIcon icon={NotificationIcon} />
|
||||
<SettingsList.ItemText>
|
||||
<Trans>Notifications</Trans>
|
||||
</SettingsList.ItemText>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
to="/settings/content-and-media"
|
||||
label={_(msg`Content and media`)}>
|
||||
|
||||
Reference in New Issue
Block a user