Files
bsky-social-app/src/screens/Settings/NotificationSettings/index.tsx
T

413 lines
16 KiB
TypeScript

import {useEffect} from 'react'
import {Linking, View} from 'react-native'
import * as Notification from 'expo-notifications'
import {Trans, useLingui} from '@lingui/react/macro'
import {useQuery, useQueryClient} from '@tanstack/react-query'
import {useAppState} from '#/lib/appState'
import {
type AllNavigatorParams,
type NativeStackScreenProps,
} from '#/lib/routes/types'
import {
useChatNotificationSettingsQuery,
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'
import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope'
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 {Message_Stroke2_Corner0_Rounded as MessageIcon} from '#/components/icons/Message'
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 {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
import * as SettingsList from '../components/SettingsList'
import {ChatNotificationDialogs} from './components/ChatNotificationDialogs'
import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
import {SettingPreview} from './components/SettingPreview'
const RQKEY = ['notification-permissions']
type Props = NativeStackScreenProps<AllNavigatorParams, 'NotificationSettings'>
export function NotificationSettingsScreen({}: Props) {
const {t: l} = useLingui()
const queryClient = useQueryClient()
const {data: settings, isError} = useNotificationSettingsQuery()
const {data: chatSettings, isError: chatError} =
useChatNotificationSettingsQuery()
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 chatDialogControl = Dialog.useDialogControl()
const chatRequestDialogControl = Dialog.useDialogControl()
const miscDialogControl = Dialog.useDialogControl()
const {data: permissions, refetch} = useQuery({
queryKey: RQKEY,
queryFn: async () => {
if (IS_WEB) return null
return await Notification.getPermissionsAsync()
},
})
const appState = useAppState()
useEffect(() => {
if (appState === 'active') {
void refetch()
}
}, [appState, refetch])
const onRequestPermissions = async () => {
if (IS_WEB) return
if (permissions?.canAskAgain) {
const response = await Notification.requestPermissionsAsync({
ios: {
/*
* These default to true only when no argument is passed, so set them
* explicitly to preserve behavior alongside
* provideAppNotificationSettings.
*/
allowAlert: true,
allowBadge: true,
allowSound: true,
provideAppNotificationSettings: true,
},
})
queryClient.setQueryData(RQKEY, response)
} else {
if (IS_ANDROID) {
try {
await Linking.sendIntent(
'android.settings.APP_NOTIFICATION_SETTINGS',
[
{
key: 'android.provider.extra.APP_PACKAGE',
value: 'xyz.blueskyweb.app',
},
],
)
} catch {
void Linking.openSettings()
}
} else if (IS_IOS) {
void 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={l`Enable push notifications`}
onPress={() => void 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.PressableItem
label={l`Settings for like notifications`}
onPress={likeDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={HeartIcon} />
<ItemTextWithSubtitle
titleText={<Trans>Likes</Trans>}
subtitleText={<SettingPreview preference={settings?.like} />}
showSkeleton={!settings}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for new follower notifications`}
onPress={followDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={PersonPlusIcon} />
<ItemTextWithSubtitle
titleText={<Trans>New followers</Trans>}
subtitleText={<SettingPreview preference={settings?.follow} />}
showSkeleton={!settings}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for reply notifications`}
onPress={replyDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={BubbleIcon} />
<ItemTextWithSubtitle
titleText={<Trans>Replies</Trans>}
subtitleText={<SettingPreview preference={settings?.reply} />}
showSkeleton={!settings}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for mention notifications`}
onPress={mentionDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={AtIcon} />
<ItemTextWithSubtitle
titleText={<Trans>Mentions</Trans>}
subtitleText={<SettingPreview preference={settings?.mention} />}
showSkeleton={!settings}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for quote notifications`}
onPress={quoteDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={CloseQuoteIcon} />
<ItemTextWithSubtitle
titleText={<Trans>Quotes</Trans>}
subtitleText={<SettingPreview preference={settings?.quote} />}
showSkeleton={!settings}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for repost notifications`}
onPress={repostDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={RepostIcon} />
<ItemTextWithSubtitle
titleText={<Trans>Reposts</Trans>}
subtitleText={<SettingPreview preference={settings?.repost} />}
showSkeleton={!settings}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for activity from others`}
onPress={activityDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={BellRingingIcon} />
<ItemTextWithSubtitle
titleText={<Trans>Activity from others</Trans>}
subtitleText={
<SettingPreview preference={settings?.subscribedPost} />
}
showSkeleton={!settings}
/>
</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
titleText={<Trans>Likes of your reposts</Trans>}
subtitleText={
<SettingPreview preference={settings?.likeViaRepost} />
}
showSkeleton={!settings}
/>
</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
titleText={<Trans>Reposts of your reposts</Trans>}
subtitleText={
<SettingPreview preference={settings?.repostViaRepost} />
}
showSkeleton={!settings}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for notifications for new messages`}
onPress={chatDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={MessageIcon} />
<ItemTextWithSubtitle
titleText={<Trans>New messages</Trans>}
subtitleText={
chatError ? (
<Trans>Failed to load notification settings.</Trans>
) : (
<SettingPreview preference={chatSettings?.chat} />
)
}
showSkeleton={!chatSettings && !chatError}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for notifications for new message requests`}
onPress={chatRequestDialogControl.open}
contentContainerStyle={[a.align_start]}>
<SettingsList.ItemIcon icon={EnvelopeIcon} />
<ItemTextWithSubtitle
titleText={<Trans>New message requests</Trans>}
subtitleText={
chatError ? (
<Trans>Failed to load notification settings.</Trans>
) : (
<SettingPreview preference={chatSettings?.chatRequest} />
)
}
showSkeleton={!chatSettings && !chatError}
/>
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={l`Settings for notifications for everything else`}
onPress={miscDialogControl.open}
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.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>
}
/>
<ChatNotificationDialogs
chatControl={chatDialogControl}
chatRequestControl={chatRequestDialogControl}
/>
<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>
)
}