forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useEffect} from 'react'
2import {Linking, View} from 'react-native'
3import * as Notification from 'expo-notifications'
4import {type AppBskyNotificationDefs} from '@atproto/api'
5import {msg} from '@lingui/core/macro'
6import {useLingui} from '@lingui/react'
7import {Trans} from '@lingui/react/macro'
8import {useQuery, useQueryClient} from '@tanstack/react-query'
9
10import {useAppState} from '#/lib/appState'
11import {
12 type AllNavigatorParams,
13 type NativeStackScreenProps,
14} from '#/lib/routes/types'
15import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
16import {atoms as a} from '#/alf'
17import {Admonition} from '#/components/Admonition'
18import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At'
19import {BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging'
20import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble'
21import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic'
22import {
23 Heart2_Stroke2_Corner0_Rounded as HeartIcon,
24 LikeRepost_Stroke2_Corner2_Rounded as LikeRepostIcon,
25} from '#/components/icons/Heart2'
26import {PersonPlus_Stroke2_Corner2_Rounded as PersonPlusIcon} from '#/components/icons/Person'
27import {CloseQuote_Stroke2_Corner0_Rounded as CloseQuoteIcon} from '#/components/icons/Quote'
28import {
29 Repost_Stroke2_Corner2_Rounded as RepostIcon,
30 RepostRepost_Stroke2_Corner2_Rounded as RepostRepostIcon,
31} from '#/components/icons/Repost'
32import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes'
33import * as Layout from '#/components/Layout'
34import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
35import * as SettingsList from '../components/SettingsList'
36import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle'
37
38const RQKEY = ['notification-permissions']
39
40type Props = NativeStackScreenProps<AllNavigatorParams, 'NotificationSettings'>
41export function NotificationSettingsScreen({}: Props) {
42 const {_} = useLingui()
43 const queryClient = useQueryClient()
44 const {data: settings, isError} = useNotificationSettingsQuery()
45
46 const {data: permissions, refetch} = useQuery({
47 queryKey: RQKEY,
48 queryFn: async () => {
49 if (IS_WEB) return null
50 return await Notification.getPermissionsAsync()
51 },
52 })
53
54 const appState = useAppState()
55 useEffect(() => {
56 if (appState === 'active') {
57 refetch()
58 }
59 }, [appState, refetch])
60
61 const onRequestPermissions = async () => {
62 if (IS_WEB) return
63 if (permissions?.canAskAgain) {
64 const response = await Notification.requestPermissionsAsync()
65 queryClient.setQueryData(RQKEY, response)
66 } else {
67 if (IS_ANDROID) {
68 try {
69 await Linking.sendIntent(
70 'android.settings.APP_NOTIFICATION_SETTINGS',
71 [
72 {
73 key: 'android.provider.extra.APP_PACKAGE',
74 value: 'app.witchsky',
75 },
76 ],
77 )
78 } catch {
79 Linking.openSettings()
80 }
81 } else if (IS_IOS) {
82 Linking.openSettings()
83 }
84 }
85 }
86
87 return (
88 <Layout.Screen>
89 <Layout.Header.Outer>
90 <Layout.Header.BackButton />
91 <Layout.Header.Content>
92 <Layout.Header.TitleText>
93 <Trans>Notifications</Trans>
94 </Layout.Header.TitleText>
95 </Layout.Header.Content>
96 <Layout.Header.Slot />
97 </Layout.Header.Outer>
98 <Layout.Content>
99 <SettingsList.Container>
100 {permissions && !permissions.granted && (
101 <>
102 <SettingsList.PressableItem
103 label={_(msg`Enable push notifications`)}
104 onPress={onRequestPermissions}>
105 <SettingsList.ItemIcon icon={HapticIcon} />
106 <SettingsList.ItemText>
107 <Trans>Enable push notifications</Trans>
108 </SettingsList.ItemText>
109 </SettingsList.PressableItem>
110 <SettingsList.Divider />
111 </>
112 )}
113 {isError && (
114 <View style={[a.px_lg, a.pb_md]}>
115 <Admonition type="error">
116 <Trans>Failed to load notification settings.</Trans>
117 </Admonition>
118 </View>
119 )}
120 <View style={[a.gap_sm]}>
121 <SettingsList.LinkItem
122 label={_(msg`Settings for like notifications`)}
123 to={{screen: 'LikeNotificationSettings'}}
124 contentContainerStyle={[a.align_start]}>
125 <SettingsList.ItemIcon icon={HeartIcon} />
126 <ItemTextWithSubtitle
127 titleText={<Trans>Likes</Trans>}
128 subtitleText={<SettingPreview preference={settings?.like} />}
129 showSkeleton={!settings}
130 />
131 </SettingsList.LinkItem>
132 <SettingsList.LinkItem
133 label={_(msg`Settings for new follower notifications`)}
134 to={{screen: 'NewFollowerNotificationSettings'}}
135 contentContainerStyle={[a.align_start]}>
136 <SettingsList.ItemIcon icon={PersonPlusIcon} />
137 <ItemTextWithSubtitle
138 titleText={<Trans>New followers</Trans>}
139 subtitleText={<SettingPreview preference={settings?.follow} />}
140 showSkeleton={!settings}
141 />
142 </SettingsList.LinkItem>
143 <SettingsList.LinkItem
144 label={_(msg`Settings for reply notifications`)}
145 to={{screen: 'ReplyNotificationSettings'}}
146 contentContainerStyle={[a.align_start]}>
147 <SettingsList.ItemIcon icon={BubbleIcon} />
148 <ItemTextWithSubtitle
149 titleText={<Trans>Replies</Trans>}
150 subtitleText={<SettingPreview preference={settings?.reply} />}
151 showSkeleton={!settings}
152 />
153 </SettingsList.LinkItem>
154 <SettingsList.LinkItem
155 label={_(msg`Settings for mention notifications`)}
156 to={{screen: 'MentionNotificationSettings'}}
157 contentContainerStyle={[a.align_start]}>
158 <SettingsList.ItemIcon icon={AtIcon} />
159 <ItemTextWithSubtitle
160 titleText={<Trans>Mentions</Trans>}
161 subtitleText={<SettingPreview preference={settings?.mention} />}
162 showSkeleton={!settings}
163 />
164 </SettingsList.LinkItem>
165 <SettingsList.LinkItem
166 label={_(msg`Settings for quote notifications`)}
167 to={{screen: 'QuoteNotificationSettings'}}
168 contentContainerStyle={[a.align_start]}>
169 <SettingsList.ItemIcon icon={CloseQuoteIcon} />
170 <ItemTextWithSubtitle
171 titleText={<Trans>Quotes</Trans>}
172 subtitleText={<SettingPreview preference={settings?.quote} />}
173 showSkeleton={!settings}
174 />
175 </SettingsList.LinkItem>
176 <SettingsList.LinkItem
177 label={_(msg`Settings for repost notifications`)}
178 to={{screen: 'RepostNotificationSettings'}}
179 contentContainerStyle={[a.align_start]}>
180 <SettingsList.ItemIcon icon={RepostIcon} />
181 <ItemTextWithSubtitle
182 titleText={<Trans>Reposts</Trans>}
183 subtitleText={<SettingPreview preference={settings?.repost} />}
184 showSkeleton={!settings}
185 />
186 </SettingsList.LinkItem>
187 <SettingsList.LinkItem
188 label={_(msg`Settings for activity from others`)}
189 to={{screen: 'ActivityNotificationSettings'}}
190 contentContainerStyle={[a.align_start]}>
191 <SettingsList.ItemIcon icon={BellRingingIcon} />
192 <ItemTextWithSubtitle
193 titleText={<Trans>Activity from others</Trans>}
194 subtitleText={
195 <SettingPreview preference={settings?.subscribedPost} />
196 }
197 showSkeleton={!settings}
198 />
199 </SettingsList.LinkItem>
200 <SettingsList.LinkItem
201 label={_(
202 msg`Settings for notifications for likes of your reposts`,
203 )}
204 to={{screen: 'LikesOnRepostsNotificationSettings'}}
205 contentContainerStyle={[a.align_start]}>
206 <SettingsList.ItemIcon icon={LikeRepostIcon} />
207 <ItemTextWithSubtitle
208 titleText={<Trans>Likes of your reposts</Trans>}
209 subtitleText={
210 <SettingPreview preference={settings?.likeViaRepost} />
211 }
212 showSkeleton={!settings}
213 />
214 </SettingsList.LinkItem>
215 <SettingsList.LinkItem
216 label={_(
217 msg`Settings for notifications for reposts of your reposts`,
218 )}
219 to={{screen: 'RepostsOnRepostsNotificationSettings'}}
220 contentContainerStyle={[a.align_start]}>
221 <SettingsList.ItemIcon icon={RepostRepostIcon} />
222 <ItemTextWithSubtitle
223 titleText={<Trans>Reposts of your reposts</Trans>}
224 subtitleText={
225 <SettingPreview preference={settings?.repostViaRepost} />
226 }
227 showSkeleton={!settings}
228 />
229 </SettingsList.LinkItem>
230 <SettingsList.LinkItem
231 label={_(msg`Settings for notifications for everything else`)}
232 to={{screen: 'MiscellaneousNotificationSettings'}}
233 contentContainerStyle={[a.align_start]}>
234 <SettingsList.ItemIcon icon={ShapesIcon} />
235 <ItemTextWithSubtitle
236 titleText={<Trans>Everything else</Trans>}
237 // technically a bundle of several settings, but since they're set together
238 // and are most likely in sync we'll just show the state of one of them
239 subtitleText={
240 <SettingPreview preference={settings?.starterpackJoined} />
241 }
242 showSkeleton={!settings}
243 />
244 </SettingsList.LinkItem>
245 </View>
246 </SettingsList.Container>
247 </Layout.Content>
248 </Layout.Screen>
249 )
250}
251
252function SettingPreview({
253 preference,
254}: {
255 preference?:
256 | AppBskyNotificationDefs.Preference
257 | AppBskyNotificationDefs.FilterablePreference
258}) {
259 const {_} = useLingui()
260 if (!preference) {
261 return null
262 } else {
263 if ('include' in preference) {
264 if (preference.include === 'all') {
265 if (preference.list && preference.push) {
266 return _(msg`In-app, Push, Everyone`)
267 } else if (preference.list) {
268 return _(msg`In-app, Everyone`)
269 } else if (preference.push) {
270 return _(msg`Push, Everyone`)
271 }
272 } else if (preference.include === 'follows') {
273 if (preference.list && preference.push) {
274 return _(msg`In-app, Push, People you follow`)
275 } else if (preference.list) {
276 return _(msg`In-app, People you follow`)
277 } else if (preference.push) {
278 return _(msg`Push, People you follow`)
279 }
280 }
281 } else {
282 if (preference.list && preference.push) {
283 return _(msg`In-app, Push`)
284 } else if (preference.list) {
285 return _(msg`In-app`)
286 } else if (preference.push) {
287 return _(msg`Push`)
288 }
289 }
290 }
291
292 return _(msg`Off`)
293}