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