Files
bsky-social-app/src/screens/Settings/NotificationSettings/components/PreferenceControls.tsx
T
2026-07-22 15:01:24 -07:00

196 lines
5.6 KiB
TypeScript

import {useMemo} from 'react'
import {View} from 'react-native'
import {Trans, useLingui} from '@lingui/react/macro'
import {
type NotificationSettingsPreference,
type NotificationSettingsPreferenceName,
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 {useAnalytics} from '#/analytics'
import {Divider} from '../../components/SettingsList'
export function PreferenceControls({
name,
syncOthers,
preference,
allowDisableInApp = true,
}: {
name: NotificationSettingsPreferenceName
/**
* 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?: NotificationSettingsPreferenceName[]
preference?: NotificationSettingsPreference
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: NotificationSettingsPreferenceName
syncOthers?: NotificationSettingsPreferenceName[]
preference: NotificationSettingsPreference
allowDisableInApp: boolean
}) {
const t = useTheme()
const {t: l} = useLingui()
const ax = useAnalytics()
const {mutate} = useNotificationSettingsUpdateMutation()
const channels = useMemo(() => {
const arr = []
if ('list' in preference && preference.list) arr.push('list')
if (preference.push) arr.push('push')
return arr
}, [preference])
const onChangeChannels = (change: string[]) => {
const newPreference = {
...preference,
...('list' in preference ? {list: change.includes('list')} : {}),
push: change.includes('push'),
} as typeof preference
const metrics: {name: string; push: boolean; list?: boolean} = {
name,
push: newPreference.push,
}
if ('list' in newPreference) {
metrics.list = newPreference.list
}
ax.metric('activityPreference:changeChannels', metrics)
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,
include: change,
} satisfies typeof preference
ax.metric('activityPreference:changeFilter', {name, value: change})
mutate({
[name]: newPreference,
...Object.fromEntries(syncOthers.map(key => [key, newPreference])),
})
}
return (
<View style={[a.gap_sm]}>
<Toggle.Group
type="checkbox"
label={l`Select your preferred notification channels`}
values={channels}
onChange={onChangeChannels}>
<View style={[a.gap_sm]}>
<Toggle.Item
label={l`Receive push notifications`}
name="push"
style={[
a.py_xs,
platform({
native: [a.justify_between],
web: [a.flex_row_reverse, a.gap_sm],
}),
]}>
<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 && 'list' in preference && (
<Toggle.Item
label={l`Receive in-app notifications`}
name="list"
style={[
a.py_xs,
platform({
native: [a.justify_between],
web: [a.flex_row_reverse, a.gap_sm],
}),
]}>
<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>
{'include' in preference && (
<>
<Divider />
<Text style={[a.font_semi_bold, a.text_md]}>
<Trans comment="Filter who you receive notifications from">
From
</Trans>
</Text>
<Toggle.Group
type="radio"
label={l`Filter who you receive notifications from`}
values={[preference.include]}
onChange={onChangeFilter}
disabled={channels.length === 0}>
<View style={[a.gap_sm]}>
<Toggle.Item highlightRow label={l`Everyone`} name="all">
{({selected}) => (
<Toggle.RadioWithLabel
label={l`Everyone`}
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>
</>
)}
</View>
)
}