forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 💫
1import {useState} from 'react'
2import {View} from 'react-native'
3import {type ProfileViewBasic} from '@atproto/api/dist/client/types/app/bsky/actor/defs'
4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6import {type NativeStackScreenProps} from '@react-navigation/native-stack'
7
8import {usePalette} from '#/lib/hooks/usePalette'
9import {type CommonNavigatorParams} from '#/lib/routes/types'
10import * as persisted from '#/state/persisted'
11import {useGoLinksEnabled, useSetGoLinksEnabled} from '#/state/preferences'
12import {
13 useConstellationInstance,
14 useSetConstellationInstance,
15} from '#/state/preferences/constellation-instance'
16import {
17 useDeerVerificationEnabled,
18 useDeerVerificationTrusted,
19 useSetDeerVerificationEnabled,
20} from '#/state/preferences/deer-verification'
21import {
22 useDirectFetchRecords,
23 useSetDirectFetchRecords,
24} from '#/state/preferences/direct-fetch-records'
25import {
26 useDisableComposerPrompt,
27 useSetDisableComposerPrompt,
28} from '#/state/preferences/disable-composer-prompt'
29import {
30 useDisableFollowedByMetrics,
31 useSetDisableFollowedByMetrics,
32} from '#/state/preferences/disable-followed-by-metrics'
33import {
34 useDisableFollowersMetrics,
35 useSetDisableFollowersMetrics,
36} from '#/state/preferences/disable-followers-metrics'
37import {
38 useDisableFollowingMetrics,
39 useSetDisableFollowingMetrics,
40} from '#/state/preferences/disable-following-metrics'
41import {
42 useDisableLikesMetrics,
43 useSetDisableLikesMetrics,
44} from '#/state/preferences/disable-likes-metrics'
45import {
46 useDisablePostsMetrics,
47 useSetDisablePostsMetrics,
48} from '#/state/preferences/disable-posts-metrics'
49import {
50 useDisableQuotesMetrics,
51 useSetDisableQuotesMetrics,
52} from '#/state/preferences/disable-quotes-metrics'
53import {
54 useDisableReplyMetrics,
55 useSetDisableReplyMetrics,
56} from '#/state/preferences/disable-reply-metrics'
57import {
58 useDisableRepostsMetrics,
59 useSetDisableRepostsMetrics,
60} from '#/state/preferences/disable-reposts-metrics'
61import {
62 useDisableSavesMetrics,
63 useSetDisableSavesMetrics,
64} from '#/state/preferences/disable-saves-metrics'
65import {
66 useDisableVerifyEmailReminder,
67 useSetDisableVerifyEmailReminder,
68} from '#/state/preferences/disable-verify-email-reminder'
69import {
70 useDisableViaRepostNotification,
71 useSetDisableViaRepostNotification,
72} from '#/state/preferences/disable-via-repost-notification'
73import {
74 useSetShowExternalShareButtons,
75 useShowExternalShareButtons,
76} from '#/state/preferences/external-share-buttons'
77import {
78 useHideFeedsPromoTab,
79 useSetHideFeedsPromoTab,
80} from '#/state/preferences/hide-feeds-promo-tab'
81import {
82 useHideSimilarAccountsRecomm,
83 useSetHideSimilarAccountsRecomm,
84} from '#/state/preferences/hide-similar-accounts-recommendations'
85import {
86 useHideUnreplyablePosts,
87 useSetHideUnreplyablePosts,
88} from '#/state/preferences/hide-unreplyable-posts'
89import {
90 useHighQualityImages,
91 useSetHighQualityImages,
92} from '#/state/preferences/high-quality-images'
93import {useModerationOpts} from '#/state/preferences/moderation-opts'
94import {
95 useNoAppLabelers,
96 useSetNoAppLabelers,
97} from '#/state/preferences/no-app-labelers'
98import {
99 useNoDiscoverFallback,
100 useSetNoDiscoverFallback,
101} from '#/state/preferences/no-discover-fallback'
102import {
103 usePostReplacement,
104 useSetPostReplacement,
105} from '#/state/preferences/post-name-replacement'
106import {
107 useRepostCarouselEnabled,
108 useSetRepostCarouselEnabled,
109} from '#/state/preferences/repost-carousel-enabled'
110import {
111 useSetShowLinkInHandle,
112 useShowLinkInHandle,
113} from '#/state/preferences/show-link-in-handle.tsx'
114import {
115 useLibreTranslateInstance,
116 useSetLibreTranslateInstance,
117 useSetTranslationServicePreference,
118 useTranslationServicePreference,
119} from '#/state/preferences/translation-service-preference'
120import {useProfilesQuery} from '#/state/queries/profile'
121import * as SettingsList from '#/screens/Settings/components/SettingsList'
122import {atoms as a, useBreakpoints} from '#/alf'
123import {Admonition} from '#/components/Admonition'
124import {Button, ButtonText} from '#/components/Button'
125import * as Dialog from '#/components/Dialog'
126import * as TextField from '#/components/forms/TextField'
127import * as Toggle from '#/components/forms/Toggle'
128import {Atom_Stroke2_Corner0_Rounded as DeerIcon} from '#/components/icons/Atom'
129import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
130import {Eye_Stroke2_Corner0_Rounded as VisibilityIcon} from '#/components/icons/Eye'
131import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe'
132import {Lab_Stroke2_Corner0_Rounded as _BeakerIcon} from '#/components/icons/Lab'
133import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller'
134import {Pencil_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil'
135import {RaisingHand4Finger_Stroke2_Corner0_Rounded as RaisingHandIcon} from '#/components/icons/RaisingHand'
136import {Star_Stroke2_Corner0_Rounded as StarIcon} from '#/components/icons/Star'
137import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified'
138import * as Layout from '#/components/Layout'
139import {InlineLinkText} from '#/components/Link'
140import {Text} from '#/components/Typography'
141import {IS_WEB} from '#/env'
142import {SearchProfileCard} from '../Search/components/SearchProfileCard'
143
144type Props = NativeStackScreenProps<CommonNavigatorParams>
145
146function ConstellationInstanceDialog({
147 control,
148}: {
149 control: Dialog.DialogControlProps
150}) {
151 const pal = usePalette('default')
152 const {_} = useLingui()
153
154 const constellationInstance = useConstellationInstance()
155 const [url, setUrl] = useState(constellationInstance ?? '')
156 const setConstellationInstance = useSetConstellationInstance()
157
158 const submit = () => {
159 setConstellationInstance(url)
160 control.close()
161 }
162
163 const shouldDisable = () => {
164 try {
165 return !new URL(url).hostname.includes('.')
166 } catch (e) {
167 return true
168 }
169 }
170
171 return (
172 <Dialog.Outer
173 control={control}
174 nativeOptions={{preventExpansion: true}}
175 onClose={() => setUrl(constellationInstance ?? '')}>
176 <Dialog.Handle />
177 <Dialog.ScrollableInner label={_(msg`Constellations instance URL`)}>
178 <View style={[a.gap_sm, a.pb_lg]}>
179 <Text style={[a.text_2xl, a.font_bold]}>
180 <Trans>Constellations instance URL</Trans>
181 </Text>
182 </View>
183
184 <View style={a.gap_lg}>
185 <Dialog.Input
186 label="Text input field"
187 autoFocus
188 style={[styles.textInput, pal.border, pal.text]}
189 onChangeText={value => {
190 setUrl(value)
191 }}
192 placeholder={persisted.defaults.constellationInstance}
193 placeholderTextColor={pal.colors.textLight}
194 onSubmitEditing={submit}
195 accessibilityHint={_(
196 msg`Input the url of the constellations instance to use`,
197 )}
198 defaultValue={constellationInstance}
199 />
200
201 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
202 <Button
203 label={_(msg`Save`)}
204 size="large"
205 onPress={submit}
206 variant="solid"
207 color="primary"
208 disabled={shouldDisable()}>
209 <ButtonText>
210 <Trans>Save</Trans>
211 </ButtonText>
212 </Button>
213 </View>
214 </View>
215
216 <Dialog.Close />
217 </Dialog.ScrollableInner>
218 </Dialog.Outer>
219 )
220}
221
222function LibreTranslateInstanceDialog({
223 control,
224}: {
225 control: Dialog.DialogControlProps
226}) {
227 const pal = usePalette('default')
228 const {_} = useLingui()
229
230 const libreTranslateInstance = useLibreTranslateInstance()
231 const [url, setUrl] = useState(libreTranslateInstance ?? '')
232 const setLibreTranslateInstance = useSetLibreTranslateInstance()
233
234 const submit = () => {
235 setLibreTranslateInstance(url)
236 control.close()
237 }
238
239 const shouldDisable = () => {
240 try {
241 return !new URL(url).hostname.includes('.')
242 } catch (e) {
243 return true
244 }
245 }
246
247 return (
248 <Dialog.Outer
249 control={control}
250 nativeOptions={{preventExpansion: true}}
251 onClose={() => setUrl(libreTranslateInstance ?? '')}>
252 <Dialog.Handle />
253 <Dialog.ScrollableInner label={_(msg`LibreTranslate instance URL`)}>
254 <View style={[a.gap_sm, a.pb_lg]}>
255 <Text style={[a.text_2xl, a.font_bold]}>
256 <Trans>LibreTranslate instance URL</Trans>
257 </Text>
258 </View>
259
260 <View style={a.gap_lg}>
261 <Dialog.Input
262 label="Text input field"
263 autoFocus
264 style={[styles.textInput, pal.border, pal.text]}
265 onChangeText={value => {
266 setUrl(value)
267 }}
268 placeholder={persisted.defaults.libreTranslateInstance}
269 placeholderTextColor={pal.colors.textLight}
270 onSubmitEditing={submit}
271 accessibilityHint={_(
272 msg`Input the url of the LibreTranslate instance to use`,
273 )}
274 defaultValue={libreTranslateInstance}
275 />
276
277 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
278 <Button
279 label={_(msg`Save`)}
280 size="large"
281 onPress={submit}
282 variant="solid"
283 color="primary"
284 disabled={shouldDisable()}>
285 <ButtonText>
286 <Trans>Save</Trans>
287 </ButtonText>
288 </Button>
289 </View>
290 </View>
291
292 <Dialog.Close />
293 </Dialog.ScrollableInner>
294 </Dialog.Outer>
295 )
296}
297
298function TrustedVerifiersDialog({
299 control,
300}: {
301 control: Dialog.DialogControlProps
302}) {
303 const {_} = useLingui()
304
305 return (
306 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
307 <Dialog.Handle />
308 <Dialog.ScrollableInner label={_(msg`Trusted Verifiers`)}>
309 <View style={[a.gap_sm, a.pb_lg]}>
310 <Text style={[a.text_2xl, a.font_bold]}>
311 <Trans>Trusted Verifiers</Trans>
312 </Text>
313 </View>
314
315 <TrustedVerifiers />
316
317 <Dialog.Close />
318 </Dialog.ScrollableInner>
319 </Dialog.Outer>
320 )
321}
322
323const TrustedVerifiers = (): React.ReactNode => {
324 const trusted = useDeerVerificationTrusted()
325 const moderationOpts = useModerationOpts()
326
327 const results = useProfilesQuery({
328 handles: Array.from(trusted),
329 })
330
331 const {gtMobile} = useBreakpoints()
332
333 return (
334 results.data &&
335 moderationOpts !== undefined && (
336 <View style={[gtMobile ? a.pl_md : a.pl_sm, a.pb_sm]}>
337 {results.data.profiles.map(profile => (
338 <SearchProfileCard
339 key={profile.did}
340 profile={profile as ProfileViewBasic}
341 moderationOpts={moderationOpts}
342 />
343 ))}
344 </View>
345 )
346 )
347}
348
349export function DeerSettingsScreen({}: Props) {
350 const {_} = useLingui()
351
352 const goLinksEnabled = useGoLinksEnabled()
353 const setGoLinksEnabled = useSetGoLinksEnabled()
354
355 const directFetchRecords = useDirectFetchRecords()
356 const setDirectFetchRecords = useSetDirectFetchRecords()
357
358 const showExternalShareButtons = useShowExternalShareButtons()
359 const setShowExternalShareButtons = useSetShowExternalShareButtons()
360
361 const noAppLabelers = useNoAppLabelers()
362 const setNoAppLabelers = useSetNoAppLabelers()
363
364 const noDiscoverFallback = useNoDiscoverFallback()
365 const setNoDiscoverFallback = useSetNoDiscoverFallback()
366
367 const highQualityImages = useHighQualityImages()
368 const setHighQualityImages = useSetHighQualityImages()
369
370 const hideFeedsPromoTab = useHideFeedsPromoTab()
371 const setHideFeedsPromoTab = useSetHideFeedsPromoTab()
372
373 const disableViaRepostNotification = useDisableViaRepostNotification()
374 const setDisableViaRepostNotification = useSetDisableViaRepostNotification()
375
376 const disableComposerPrompt = useDisableComposerPrompt()
377 const setDisableComposerPrompt = useSetDisableComposerPrompt()
378
379 const disableLikesMetrics = useDisableLikesMetrics()
380 const setDisableLikesMetrics = useSetDisableLikesMetrics()
381
382 const disableRepostsMetrics = useDisableRepostsMetrics()
383 const setDisableRepostsMetrics = useSetDisableRepostsMetrics()
384
385 const disableQuotesMetrics = useDisableQuotesMetrics()
386 const setDisableQuotesMetrics = useSetDisableQuotesMetrics()
387
388 const disableSavesMetrics = useDisableSavesMetrics()
389 const setDisableSavesMetrics = useSetDisableSavesMetrics()
390
391 const disableReplyMetrics = useDisableReplyMetrics()
392 const setDisableReplyMetrics = useSetDisableReplyMetrics()
393
394 const disableFollowersMetrics = useDisableFollowersMetrics()
395 const setDisableFollowersMetrics = useSetDisableFollowersMetrics()
396
397 const disableFollowingMetrics = useDisableFollowingMetrics()
398 const setDisableFollowingMetrics = useSetDisableFollowingMetrics()
399
400 const disableFollowedByMetrics = useDisableFollowedByMetrics()
401 const setDisableFollowedByMetrics = useSetDisableFollowedByMetrics()
402
403 const disablePostsMetrics = useDisablePostsMetrics()
404 const setDisablePostsMetrics = useSetDisablePostsMetrics()
405
406 const hideSimilarAccountsRecomm = useHideSimilarAccountsRecomm()
407 const setHideSimilarAccountsRecomm = useSetHideSimilarAccountsRecomm()
408
409 const hideUnreplyablePosts = useHideUnreplyablePosts()
410 const setHideUnreplyablePosts = useSetHideUnreplyablePosts()
411
412 const disableVerifyEmailReminder = useDisableVerifyEmailReminder()
413 const setDisableVerifyEmailReminder = useSetDisableVerifyEmailReminder()
414
415 const constellationInstance = useConstellationInstance()
416 const setConstellationInstanceControl = Dialog.useDialogControl()
417
418 const setTrustedVerifiersDialogControl = Dialog.useDialogControl()
419
420 const deerVerificationEnabled = useDeerVerificationEnabled()
421 const setDeerVerificationEnabled = useSetDeerVerificationEnabled()
422
423 const repostCarouselEnabled = useRepostCarouselEnabled()
424 const setRepostCarouselEnabled = useSetRepostCarouselEnabled()
425
426 const showLinkInHandle = useShowLinkInHandle()
427 const setShowLinkInHandle = useSetShowLinkInHandle()
428
429 const translationServicePreference = useTranslationServicePreference()
430 const setTranslationServicePreference = useSetTranslationServicePreference()
431
432 const setLibreTranslateInstanceControl = Dialog.useDialogControl()
433
434 const postReplacement = usePostReplacement()
435 const setPostReplacement = useSetPostReplacement()
436
437 return (
438 <Layout.Screen>
439 <Layout.Header.Outer>
440 <Layout.Header.BackButton />
441 <Layout.Header.Content>
442 <Layout.Header.TitleText>
443 <Trans>Experiments</Trans>
444 </Layout.Header.TitleText>
445 </Layout.Header.Content>
446 <Layout.Header.Slot />
447 </Layout.Header.Outer>
448 <Layout.Content>
449 <SettingsList.Container>
450 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
451 <SettingsList.ItemIcon icon={DeerIcon} />
452 <SettingsList.ItemText>
453 <Trans>Redirects</Trans>
454 </SettingsList.ItemText>
455 <Toggle.Item
456 name="use_go_links"
457 label={_(msg`Redirect through go.bsky.app`)}
458 value={goLinksEnabled ?? false}
459 onChange={value => setGoLinksEnabled(value)}
460 style={[a.w_full]}>
461 <Toggle.LabelText style={[a.flex_1]}>
462 <Trans>Redirect through go.bsky.app</Trans>
463 </Toggle.LabelText>
464 <Toggle.Platform />
465 </Toggle.Item>
466 </SettingsList.Group>
467
468 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
469 <SettingsList.ItemIcon icon={VisibilityIcon} />
470 <SettingsList.ItemText>
471 <Trans>Visibility</Trans>
472 </SettingsList.ItemText>
473 <Toggle.Item
474 name="direct_fetch_records"
475 label={_(
476 msg`Fetch records directly from PDS to see through quote blocks`,
477 )}
478 value={directFetchRecords}
479 onChange={value => setDirectFetchRecords(value)}
480 style={[a.w_full]}>
481 <Toggle.LabelText style={[a.flex_1]}>
482 <Trans>
483 Fetch records directly from PDS to see contents of blocked and
484 detached quotes
485 </Trans>
486 </Toggle.LabelText>
487 <Toggle.Platform />
488 </Toggle.Item>
489 </SettingsList.Group>
490
491 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
492 <SettingsList.ItemIcon icon={ChainLinkIcon} />
493 <SettingsList.ItemText>
494 <Trans>Bridging and Fediverse</Trans>
495 </SettingsList.ItemText>
496 <Toggle.Item
497 name="external_share_buttons"
498 label={_(
499 msg`Show "Open original post" and "Open post in PDSls" buttons`,
500 )}
501 value={showExternalShareButtons}
502 onChange={value => setShowExternalShareButtons(value)}
503 style={[a.w_full]}>
504 <Toggle.LabelText style={[a.flex_1]}>
505 <Trans>
506 Show "Open original post" and "Open post in PDSls" buttons
507 </Trans>
508 </Toggle.LabelText>
509 <Toggle.Platform />
510 </Toggle.Item>
511 </SettingsList.Group>
512
513 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
514 <SettingsList.ItemIcon icon={VerifiedIcon} />
515 <SettingsList.ItemText>
516 <Trans>Verification</Trans>
517 </SettingsList.ItemText>
518 <Toggle.Item
519 name="custom_verifications"
520 label={_(
521 msg`Select your own set of trusted verifiers, and operate as a verifier`,
522 )}
523 value={deerVerificationEnabled}
524 onChange={value => setDeerVerificationEnabled(value)}
525 style={[a.w_full]}>
526 <Toggle.LabelText style={[a.flex_1]}>
527 <Trans>
528 Select your own set of trusted verifiers, and operate as a
529 verifier
530 </Trans>
531 </Toggle.LabelText>
532 <Toggle.Platform />
533 </Toggle.Item>
534 </SettingsList.Group>
535
536 <SettingsList.Item>
537 <Admonition type="warning" style={[a.flex_1]}>
538 <Trans>
539 May slow down the client or fail to find all labels. Revoke and
540 grant trust in the meatball menu on a profile.{' '}
541 {deerVerificationEnabled
542 ? 'You currently'
543 : 'If enabled, you would'}{' '}
544 trust the following verifiers:
545 </Trans>
546 </Admonition>
547 </SettingsList.Item>
548
549 <SettingsList.Item>
550 <SettingsList.ItemIcon icon={VerifiedIcon} />
551 <SettingsList.ItemText>
552 <Trans>{`Trusted Verifiers`}</Trans>
553 </SettingsList.ItemText>
554 <SettingsList.BadgeButton
555 label={_(msg`View`)}
556 onPress={() => setTrustedVerifiersDialogControl.open()}
557 />
558 </SettingsList.Item>
559
560 <SettingsList.Item>
561 <SettingsList.ItemIcon icon={StarIcon} />
562 <SettingsList.ItemText>
563 <Trans>{`Constellation Instance`}</Trans>
564 </SettingsList.ItemText>
565 <SettingsList.BadgeButton
566 label={_(msg`Change`)}
567 onPress={() => setConstellationInstanceControl.open()}
568 />
569 </SettingsList.Item>
570 <SettingsList.Item>
571 <Admonition type="info" style={[a.flex_1]}>
572 <Trans>
573 Constellation is used to supplement AppView responses for custom
574 verifications and nuclear block bypass, via backlinks. Current
575 instance:
576 <InlineLinkText
577 to={constellationInstance}
578 label={constellationInstance}>
579 {constellationInstance}
580 </InlineLinkText>
581 </Trans>
582 </Admonition>
583 </SettingsList.Item>
584
585 <SettingsList.Divider />
586
587 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
588 <SettingsList.ItemIcon icon={PencilIcon} />
589 <SettingsList.ItemText>
590 <Trans>
591 Call posts{' '}
592 {postReplacement.string.length
593 ? postReplacement.string.toLowerCase()
594 : 'skeet'}
595 s
596 </Trans>
597 </SettingsList.ItemText>
598 <Toggle.Item
599 name="call_posts_skeets"
600 label={_(
601 msg`Changes post to another word of your choosing. Requires a refresh to update.`,
602 )}
603 value={postReplacement.enabled}
604 onChange={value =>
605 setPostReplacement({
606 enabled: value,
607 string: postReplacement.string,
608 })
609 }
610 style={[a.w_full]}>
611 <Toggle.LabelText style={[a.flex_1]}>
612 <Trans>
613 Changes post to another word of your choosing. Requires a
614 refresh to update.
615 </Trans>
616 </Toggle.LabelText>
617 <Toggle.Platform />
618 </Toggle.Item>
619
620 {postReplacement.enabled && (
621 <SettingsList.Item>
622 <TextField.Root>
623 <TextField.Input
624 label={_(msg`Custom post name`)}
625 value={postReplacement.string}
626 onChangeText={(value: string) =>
627 setPostReplacement(
628 (curr: {enabled: boolean; string: string}) => ({
629 ...curr,
630 string: value,
631 }),
632 )
633 }
634 />
635 </TextField.Root>
636 </SettingsList.Item>
637 )}
638 </SettingsList.Group>
639
640 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
641 <SettingsList.ItemIcon icon={PaintRollerIcon} />
642 <SettingsList.ItemText>
643 <Trans>Tweaks</Trans>
644 </SettingsList.ItemText>
645 <Toggle.Item
646 name="repost_carousel"
647 label={_(msg`Combine reposts into a horizontal carousel`)}
648 value={repostCarouselEnabled}
649 onChange={value => setRepostCarouselEnabled(value)}
650 style={[a.w_full]}>
651 <Toggle.LabelText style={[a.flex_1]}>
652 <Trans>Combine reposts into a horizontal carousel</Trans>
653 </Toggle.LabelText>
654 <Toggle.Platform />
655 </Toggle.Item>
656 <Toggle.Item
657 name="no_discover_fallback"
658 label={_(msg`Do not fall back to discover feed`)}
659 value={noDiscoverFallback}
660 onChange={value => setNoDiscoverFallback(value)}
661 style={[a.w_full]}>
662 <Toggle.LabelText style={[a.flex_1]}>
663 <Trans>Do not fall back to discover feed</Trans>
664 </Toggle.LabelText>
665 <Toggle.Platform />
666 </Toggle.Item>
667 <Toggle.Item
668 name="show_link_in_handle"
669 label={_(
670 msg`On non-bsky.social handles, show a link to that URL`,
671 )}
672 value={showLinkInHandle}
673 onChange={value => setShowLinkInHandle(value)}
674 style={[a.w_full]}>
675 <Toggle.LabelText style={[a.flex_1]}>
676 <Trans>
677 On non-bsky.social handles, show a link to that URL
678 </Trans>
679 </Toggle.LabelText>
680 <Toggle.Platform />
681 </Toggle.Item>
682
683 <Toggle.Item
684 name="repost_carousel"
685 label={_(msg`Combine reposts into a horizontal carousel`)}
686 value={repostCarouselEnabled}
687 onChange={value => setRepostCarouselEnabled(value)}
688 style={[a.w_full]}>
689 <Toggle.LabelText style={[a.flex_1]}>
690 <Trans>Combine reposts into a horizontal carousel</Trans>
691 </Toggle.LabelText>
692 <Toggle.Platform />
693 </Toggle.Item>
694
695 <Toggle.Item
696 name="no_discover_fallback"
697 label={_(msg`Do not fall back to discover feed`)}
698 value={noDiscoverFallback}
699 onChange={value => setNoDiscoverFallback(value)}
700 style={[a.w_full]}>
701 <Toggle.LabelText style={[a.flex_1]}>
702 <Trans>Do not fall back to discover feed</Trans>
703 </Toggle.LabelText>
704 <Toggle.Platform />
705 </Toggle.Item>
706
707 <Toggle.Item
708 name="high_quality_images"
709 label={_(msg`Display images in higher quality`)}
710 value={highQualityImages}
711 onChange={value => setHighQualityImages(value)}
712 style={[a.w_full]}>
713 <Toggle.LabelText style={[a.flex_1]}>
714 <Trans>Display images in higher quality</Trans>
715 </Toggle.LabelText>
716 <Toggle.Platform />
717 </Toggle.Item>
718 <Admonition type="info" style={[a.flex_1]}>
719 <Trans>
720 Images will be served as PNG instead of JPEG. Images will take
721 longer to load and use more bandwidth.
722 </Trans>
723 </Admonition>
724
725 <Toggle.Item
726 name="hide_feeds_promo_tab"
727 label={_(msg`Hide "Feeds ✨" tab when only one feed is selected`)}
728 value={hideFeedsPromoTab}
729 onChange={value => setHideFeedsPromoTab(value)}
730 style={[a.w_full]}>
731 <Toggle.LabelText style={[a.flex_1]}>
732 <Trans>
733 Hide "Feeds ✨" tab when only one feed is selected
734 </Trans>
735 </Toggle.LabelText>
736 <Toggle.Platform />
737 </Toggle.Item>
738
739 <Toggle.Item
740 name="disable_via_repost_notification"
741 label={_(msg`Disable via repost notifications`)}
742 value={disableViaRepostNotification}
743 onChange={value => setDisableViaRepostNotification(value)}
744 style={[a.w_full]}>
745 <Toggle.LabelText style={[a.flex_1]}>
746 <Trans>Disable via repost notifications</Trans>
747 </Toggle.LabelText>
748 <Toggle.Platform />
749 </Toggle.Item>
750 <Admonition type="info" style={[a.flex_1]}>
751 <Trans>
752 Forcefully disables the notifications other people receive when
753 you like/repost a post someone else has reposted for privacy.
754 </Trans>
755 </Admonition>
756
757 <Toggle.Item
758 name="hide_similar_accounts_recommendations"
759 label={_(msg`Hide similar accounts recommendations`)}
760 value={hideSimilarAccountsRecomm}
761 onChange={value => setHideSimilarAccountsRecomm(value)}
762 style={[a.w_full]}>
763 <Toggle.LabelText style={[a.flex_1]}>
764 <Trans>Hide similar accounts recommendations</Trans>
765 </Toggle.LabelText>
766 <Toggle.Platform />
767 </Toggle.Item>
768
769 <Toggle.Item
770 name="hide_unreplyable_posts"
771 label={_(msg`Hide posts that cannot be replied to from feeds`)}
772 value={hideUnreplyablePosts}
773 onChange={value => setHideUnreplyablePosts(value)}
774 style={[a.w_full]}>
775 <Toggle.LabelText style={[a.flex_1]}>
776 <Trans>Hide posts that cannot be replied to from feeds</Trans>
777 </Toggle.LabelText>
778 <Toggle.Platform />
779 </Toggle.Item>
780 <Admonition type="info" style={[a.flex_1]}>
781 <Trans>
782 Hides posts from feeds where replies are disabled (e.g. due to
783 postgates or other restrictions). Does not affect thread views.
784 </Trans>
785 </Admonition>
786
787 <Toggle.Item
788 name="disable_composer_prompt"
789 label={_(msg`Disable composer prompt`)}
790 value={disableComposerPrompt}
791 onChange={value => setDisableComposerPrompt(value)}
792 style={[a.w_full]}>
793 <Toggle.LabelText style={[a.flex_1]}>
794 <Trans>Disable composer prompt</Trans>
795 </Toggle.LabelText>
796 <Toggle.Platform />
797 </Toggle.Item>
798
799 <Toggle.Item
800 name="disable_verify_email_reminder"
801 label={_(msg`Disable verify email reminder`)}
802 value={disableVerifyEmailReminder}
803 onChange={value => setDisableVerifyEmailReminder(value)}
804 style={[a.w_full]}>
805 <Toggle.LabelText style={[a.flex_1]}>
806 <Trans>Disable verify email reminder</Trans>
807 </Toggle.LabelText>
808 <Toggle.Platform />
809 </Toggle.Item>
810 <Admonition type="warning" style={[a.flex_1]}>
811 <Trans>
812 This only gets rid of the reminder on app launch, useful if your
813 PDS does not have email verification setup.\nThis does NOT give
814 access to features locked behind email verification.
815 </Trans>
816 </Admonition>
817 </SettingsList.Group>
818
819 <SettingsList.Divider />
820
821 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
822 <SettingsList.ItemIcon icon={EarthIcon} />
823 <SettingsList.ItemText>
824 <Trans>Post Translation Engine</Trans>
825 </SettingsList.ItemText>
826
827 <Toggle.Item
828 name="service_google"
829 label={_(msg`Use Google Translate`)}
830 value={translationServicePreference === 'google'}
831 onChange={() => setTranslationServicePreference('google')}
832 style={[a.w_full]}>
833 <Toggle.LabelText style={[a.flex_1]}>
834 <Trans>Use Google Translate</Trans>
835 </Toggle.LabelText>
836 <Toggle.Radio />
837 </Toggle.Item>
838
839 <Toggle.Item
840 name="service_kagi"
841 label={_(msg`Use Kagi Translate`)}
842 value={translationServicePreference === 'kagi'}
843 onChange={() => setTranslationServicePreference('kagi')}
844 style={[a.w_full]}>
845 <Toggle.LabelText style={[a.flex_1]}>
846 <Trans>Use Kagi Translate</Trans>
847 </Toggle.LabelText>
848 <Toggle.Radio />
849 </Toggle.Item>
850
851 <Toggle.Item
852 name="service_papago"
853 label={_(msg`Use Naver Papago`)}
854 value={translationServicePreference === 'papago'}
855 onChange={() => setTranslationServicePreference('papago')}
856 style={[a.w_full]}>
857 <Toggle.LabelText style={[a.flex_1]}>
858 <Trans>Use Naver Papago</Trans>
859 </Toggle.LabelText>
860 <Toggle.Radio />
861 </Toggle.Item>
862
863 <Toggle.Item
864 name="service_libreTranslate"
865 label={_(msg`Use LibreTranslate`)}
866 value={translationServicePreference === 'libreTranslate'}
867 onChange={() => setTranslationServicePreference('libreTranslate')}
868 style={[a.w_full]}>
869 <Toggle.LabelText style={[a.flex_1]}>
870 <Trans>Use LibreTranslate</Trans>
871 </Toggle.LabelText>
872 <Toggle.Radio />
873 </Toggle.Item>
874 </SettingsList.Group>
875
876 {translationServicePreference === 'libreTranslate' && (
877 <SettingsList.Item>
878 <SettingsList.ItemIcon icon={EarthIcon} />
879 <SettingsList.ItemText>
880 <Trans>{`LibreTranslate Instance`}</Trans>
881 </SettingsList.ItemText>
882 <SettingsList.BadgeButton
883 label={_(msg`Change`)}
884 onPress={() => setLibreTranslateInstanceControl.open()}
885 />
886 </SettingsList.Item>
887 )}
888
889 <SettingsList.Divider />
890
891 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
892 <SettingsList.ItemIcon icon={VisibilityIcon} />
893 <SettingsList.ItemText>
894 <Trans>Metrics</Trans>
895 </SettingsList.ItemText>
896
897 <Toggle.Item
898 name="disable_likes_metrics"
899 label={_(msg`Disable likes metrics`)}
900 value={disableLikesMetrics}
901 onChange={value => setDisableLikesMetrics(value)}
902 style={[a.w_full]}>
903 <Toggle.LabelText style={[a.flex_1]}>
904 <Trans>Disable likes metrics</Trans>
905 </Toggle.LabelText>
906 <Toggle.Platform />
907 </Toggle.Item>
908
909 <Toggle.Item
910 name="disable_reposts_metrics"
911 label={_(msg`Disable Reposts Metrics`)}
912 value={disableRepostsMetrics}
913 onChange={value => setDisableRepostsMetrics(value)}
914 style={[a.w_full]}>
915 <Toggle.LabelText style={[a.flex_1]}>
916 <Trans>Disable Reposts Metrics</Trans>
917 </Toggle.LabelText>
918 <Toggle.Platform />
919 </Toggle.Item>
920
921 <Toggle.Item
922 name="disable_quotes_metrics"
923 label={_(msg`Disable quotes metrics`)}
924 value={disableQuotesMetrics}
925 onChange={value => setDisableQuotesMetrics(value)}
926 style={[a.w_full]}>
927 <Toggle.LabelText style={[a.flex_1]}>
928 <Trans>Disable quotes metrics</Trans>
929 </Toggle.LabelText>
930 <Toggle.Platform />
931 </Toggle.Item>
932
933 <Toggle.Item
934 name="disable_saves_metrics"
935 label={_(msg`Disable saves metrics`)}
936 value={disableSavesMetrics}
937 onChange={value => setDisableSavesMetrics(value)}
938 style={[a.w_full]}>
939 <Toggle.LabelText style={[a.flex_1]}>
940 <Trans>Disable saves metrics</Trans>
941 </Toggle.LabelText>
942 <Toggle.Platform />
943 </Toggle.Item>
944
945 <Toggle.Item
946 name="disable_reply_metrics"
947 label={_(msg`Disable reply metrics`)}
948 value={disableReplyMetrics}
949 onChange={value => setDisableReplyMetrics(value)}
950 style={[a.w_full]}>
951 <Toggle.LabelText style={[a.flex_1]}>
952 <Trans>Disable reply metrics</Trans>
953 </Toggle.LabelText>
954 <Toggle.Platform />
955 </Toggle.Item>
956
957 <Toggle.Item
958 name="disable_followers_metrics"
959 label={_(msg`Disable followers metrics`)}
960 value={disableFollowersMetrics}
961 onChange={value => setDisableFollowersMetrics(value)}
962 style={[a.w_full]}>
963 <Toggle.LabelText style={[a.flex_1]}>
964 <Trans>Disable followers metrics</Trans>
965 </Toggle.LabelText>
966 <Toggle.Platform />
967 </Toggle.Item>
968
969 <Toggle.Item
970 name="disable_following_metrics"
971 label={_(msg`Disable following metrics`)}
972 value={disableFollowingMetrics}
973 onChange={value => setDisableFollowingMetrics(value)}
974 style={[a.w_full]}>
975 <Toggle.LabelText style={[a.flex_1]}>
976 <Trans>Disable following metrics</Trans>
977 </Toggle.LabelText>
978 <Toggle.Platform />
979 </Toggle.Item>
980
981 <Toggle.Item
982 name="disable_followed_by_metrics"
983 label={_(msg`Disable "followed by" metrics`)}
984 value={disableFollowedByMetrics}
985 onChange={value => setDisableFollowedByMetrics(value)}
986 style={[a.w_full]}>
987 <Toggle.LabelText style={[a.flex_1]}>
988 <Trans>Disable "followed by" metrics</Trans>
989 </Toggle.LabelText>
990 <Toggle.Platform />
991 </Toggle.Item>
992
993 <Toggle.Item
994 name="disable_posts_metrics"
995 label={_(msg`Disable posts metrics`)}
996 value={disablePostsMetrics}
997 onChange={value => setDisablePostsMetrics(value)}
998 style={[a.w_full]}>
999 <Toggle.LabelText style={[a.flex_1]}>
1000 <Trans>Disable posts metrics</Trans>
1001 </Toggle.LabelText>
1002 <Toggle.Platform />
1003 </Toggle.Item>
1004 </SettingsList.Group>
1005
1006 <SettingsList.Divider />
1007
1008 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
1009 <SettingsList.ItemIcon icon={RaisingHandIcon} />
1010 <SettingsList.ItemText>
1011 <Trans>Labelers</Trans>
1012 </SettingsList.ItemText>
1013 <Toggle.Item
1014 name="no_app_labelers"
1015 label={_(msg`Do not declare any app labelers`)}
1016 value={noAppLabelers}
1017 onChange={value => setNoAppLabelers(value)}
1018 style={[a.w_full]}>
1019 <Toggle.LabelText style={[a.flex_1]}>
1020 <Trans>Do not declare any default app labelers</Trans>
1021 </Toggle.LabelText>
1022 <Toggle.Platform />
1023 </Toggle.Item>
1024 </SettingsList.Group>
1025
1026 <SettingsList.Item>
1027 <Admonition type="warning" style={[a.flex_1]}>
1028 <Trans>Restart the app after changing this setting.</Trans>
1029 </Admonition>
1030 </SettingsList.Item>
1031 <SettingsList.Item>
1032 <Admonition type="tip" style={[a.flex_1]}>
1033 <Trans>
1034 Some App Views will default to using an app labeler if you have
1035 no labelers, so consider subscribing to at least one labeler if
1036 you have issues.
1037 </Trans>
1038 </Admonition>
1039 </SettingsList.Item>
1040 <SettingsList.Item>
1041 <Admonition type="info" style={[a.flex_1]}>
1042 <Trans>
1043 App labelers are mandatory top-level labelers that can perform
1044 "takedowns". This setting does not influence geolocation-based
1045 labelers.
1046 </Trans>
1047 </Admonition>
1048 </SettingsList.Item>
1049 </SettingsList.Container>
1050 </Layout.Content>
1051 <ConstellationInstanceDialog control={setConstellationInstanceControl} />
1052 <TrustedVerifiersDialog control={setTrustedVerifiersDialogControl} />
1053 <LibreTranslateInstanceDialog
1054 control={setLibreTranslateInstanceControl}
1055 />
1056 </Layout.Screen>
1057 )
1058}
1059
1060const styles = {
1061 textInput: {
1062 borderWidth: 1,
1063 borderRadius: 6,
1064 paddingHorizontal: 14,
1065 paddingVertical: 10,
1066 fontSize: 16,
1067 },
1068}