forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useMemo} from 'react'
2import {View} from 'react-native'
3import {type AppBskyNotificationDefs} from '@atproto/api'
4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6
7import {useNotificationSettingsUpdateMutation} from '#/state/queries/notifications/settings'
8import {atoms as a, platform, useTheme} from '#/alf'
9import * as Toggle from '#/components/forms/Toggle'
10import {Loader} from '#/components/Loader'
11import {Text} from '#/components/Typography'
12import {useAnalytics} from '#/analytics'
13import {Divider} from '../../components/SettingsList'
14
15export function PreferenceControls({
16 name,
17 syncOthers,
18 preference,
19 allowDisableInApp = true,
20}: {
21 name: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>
22 /**
23 * Keep other prefs in sync with `name`. For use in the "everything else" category
24 * which groups starterpack joins + verified + unverified notifications into a single toggle.
25 */
26 syncOthers?: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>[]
27 preference?:
28 | AppBskyNotificationDefs.Preference
29 | AppBskyNotificationDefs.FilterablePreference
30 allowDisableInApp?: boolean
31}) {
32 if (!preference)
33 return (
34 <View style={[a.w_full, a.pt_5xl, a.align_center]}>
35 <Loader size="xl" />
36 </View>
37 )
38
39 return (
40 <Inner
41 name={name}
42 syncOthers={syncOthers}
43 preference={preference}
44 allowDisableInApp={allowDisableInApp}
45 />
46 )
47}
48
49export function Inner({
50 name,
51 syncOthers = [],
52 preference,
53 allowDisableInApp,
54}: {
55 name: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>
56 syncOthers?: Exclude<keyof AppBskyNotificationDefs.Preferences, '$type'>[]
57 preference:
58 | AppBskyNotificationDefs.Preference
59 | AppBskyNotificationDefs.FilterablePreference
60 allowDisableInApp: boolean
61}) {
62 const t = useTheme()
63 const {_} = useLingui()
64 const ax = useAnalytics()
65 const {mutate} = useNotificationSettingsUpdateMutation()
66
67 const channels = useMemo(() => {
68 const arr = []
69 if (preference.list) arr.push('list')
70 if (preference.push) arr.push('push')
71 return arr
72 }, [preference])
73
74 const onChangeChannels = (change: string[]) => {
75 const newPreference = {
76 ...preference,
77 list: change.includes('list'),
78 push: change.includes('push'),
79 } satisfies typeof preference
80
81 ax.metric('activityPreference:changeChannels', {
82 name,
83 push: newPreference.push,
84 list: newPreference.list,
85 })
86
87 mutate({
88 [name]: newPreference,
89 ...Object.fromEntries(syncOthers.map(key => [key, newPreference])),
90 })
91 }
92
93 const onChangeFilter = ([change]: string[]) => {
94 if (change !== 'all' && change !== 'follows')
95 throw new Error('Invalid filter')
96
97 const newPreference = {
98 ...preference,
99 include: change,
100 } satisfies typeof preference
101
102 ax.metric('activityPreference:changeFilter', {name, value: change})
103
104 mutate({
105 [name]: newPreference,
106 ...Object.fromEntries(syncOthers.map(key => [key, newPreference])),
107 })
108 }
109
110 return (
111 <View style={[a.px_xl, a.pt_md, a.gap_sm]}>
112 <Toggle.Group
113 type="checkbox"
114 label={_(msg`Select your preferred notification channels`)}
115 values={channels}
116 onChange={onChangeChannels}>
117 <View style={[a.gap_sm]}>
118 <Toggle.Item
119 label={_(msg`Receive push notifications`)}
120 name="push"
121 style={[
122 a.py_xs,
123 platform({
124 native: [a.justify_between],
125 web: [a.flex_row_reverse, a.gap_sm],
126 }),
127 ]}>
128 <Toggle.LabelText
129 style={[t.atoms.text, a.font_normal, a.text_md, a.flex_1]}>
130 <Trans>Push notifications</Trans>
131 </Toggle.LabelText>
132 <Toggle.Platform />
133 </Toggle.Item>
134 {allowDisableInApp && (
135 <Toggle.Item
136 label={_(msg`Receive in-app notifications`)}
137 name="list"
138 style={[
139 a.py_xs,
140 platform({
141 native: [a.justify_between],
142 web: [a.flex_row_reverse, a.gap_sm],
143 }),
144 ]}>
145 <Toggle.LabelText
146 style={[t.atoms.text, a.font_normal, a.text_md, a.flex_1]}>
147 <Trans>In-app notifications</Trans>
148 </Toggle.LabelText>
149 <Toggle.Platform />
150 </Toggle.Item>
151 )}
152 </View>
153 </Toggle.Group>
154 {'include' in preference && (
155 <>
156 <Divider />
157 <Text style={[a.font_semi_bold, a.text_md]}>
158 <Trans>From</Trans>
159 </Text>
160 <Toggle.Group
161 type="radio"
162 label={_(msg`Filter who you receive notifications from`)}
163 values={[preference.include]}
164 onChange={onChangeFilter}
165 disabled={channels.length === 0}>
166 <View style={[a.gap_sm]}>
167 <Toggle.Item
168 label={_(msg`Everyone`)}
169 name="all"
170 style={[a.flex_row, a.py_xs, a.gap_sm]}>
171 <Toggle.Radio />
172 <Toggle.LabelText
173 style={[
174 channels.length > 0 && t.atoms.text,
175 a.font_normal,
176 a.text_md,
177 ]}>
178 <Trans>Everyone</Trans>
179 </Toggle.LabelText>
180 </Toggle.Item>
181 <Toggle.Item
182 label={_(msg`People I follow`)}
183 name="follows"
184 style={[a.flex_row, a.py_xs, a.gap_sm]}>
185 <Toggle.Radio />
186 <Toggle.LabelText
187 style={[
188 channels.length > 0 && t.atoms.text,
189 a.font_normal,
190 a.text_md,
191 ]}>
192 <Trans>People I follow</Trans>
193 </Toggle.LabelText>
194 </Toggle.Item>
195 </View>
196 </Toggle.Group>
197 </>
198 )}
199 </View>
200 )
201}