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