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} from '@lingui/core/macro'
5import {useLingui} from '@lingui/react'
6import {Trans} from '@lingui/react/macro'
7import {type NativeStackScreenProps} from '@react-navigation/native-stack'
8
9import {DEFAULT_ALT_TEXT_AI_MODEL} from '#/lib/constants'
10import {usePalette} from '#/lib/hooks/usePalette'
11import {type CommonNavigatorParams} from '#/lib/routes/types'
12import {dynamicActivate} from '#/locale/i18n'
13import {dynamicActivate as dynamicActivateWeb} from '#/locale/i18n.web'
14import {type AppLanguage} from '#/locale/languages'
15import * as persisted from '#/state/persisted'
16import {useGoLinksEnabled, useSetGoLinksEnabled} from '#/state/preferences'
17import {
18 useConstellationInstance,
19 useSetConstellationInstance,
20} from '#/state/preferences/constellation-instance'
21import {
22 useDeerVerificationEnabled,
23 useDeerVerificationTrusted,
24 useSetDeerVerificationEnabled,
25} from '#/state/preferences/deer-verification'
26import {
27 useDirectFetchRecords,
28 useSetDirectFetchRecords,
29} from '#/state/preferences/direct-fetch-records'
30import {
31 useDisableComposerPrompt,
32 useSetDisableComposerPrompt,
33} from '#/state/preferences/disable-composer-prompt'
34import {
35 useDisableFollowedByMetrics,
36 useSetDisableFollowedByMetrics,
37} from '#/state/preferences/disable-followed-by-metrics'
38import {
39 useDisableFollowersMetrics,
40 useSetDisableFollowersMetrics,
41} from '#/state/preferences/disable-followers-metrics'
42import {
43 useDisableFollowingMetrics,
44 useSetDisableFollowingMetrics,
45} from '#/state/preferences/disable-following-metrics'
46import {
47 useDisableLikesMetrics,
48 useSetDisableLikesMetrics,
49} from '#/state/preferences/disable-likes-metrics'
50import {
51 useDisablePostsMetrics,
52 useSetDisablePostsMetrics,
53} from '#/state/preferences/disable-posts-metrics'
54import {
55 useDisableQuotesMetrics,
56 useSetDisableQuotesMetrics,
57} from '#/state/preferences/disable-quotes-metrics'
58import {
59 useDisableReplyMetrics,
60 useSetDisableReplyMetrics,
61} from '#/state/preferences/disable-reply-metrics'
62import {
63 useDisableRepostsMetrics,
64 useSetDisableRepostsMetrics,
65} from '#/state/preferences/disable-reposts-metrics'
66import {
67 useDisableSavesMetrics,
68 useSetDisableSavesMetrics,
69} from '#/state/preferences/disable-saves-metrics'
70import {
71 useDisableVerifyEmailReminder,
72 useSetDisableVerifyEmailReminder,
73} from '#/state/preferences/disable-verify-email-reminder'
74import {
75 useDisableViaRepostNotification,
76 useSetDisableViaRepostNotification,
77} from '#/state/preferences/disable-via-repost-notification'
78import {
79 useDiscoverContextEnabled,
80 useSetDiscoverContextEnabled,
81} from '#/state/preferences/discover-context-enabled'
82import {
83 useSetShowExternalShareButtons,
84 useShowExternalShareButtons,
85} from '#/state/preferences/external-share-buttons'
86import {
87 useHideFeedsPromoTab,
88 useSetHideFeedsPromoTab,
89} from '#/state/preferences/hide-feeds-promo-tab'
90import {
91 useHideSimilarAccountsRecomm,
92 useSetHideSimilarAccountsRecomm,
93} from '#/state/preferences/hide-similar-accounts-recommendations'
94import {
95 useHideUnreplyablePosts,
96 useSetHideUnreplyablePosts,
97} from '#/state/preferences/hide-unreplyable-posts'
98import {
99 useHighQualityImages,
100 useSetHighQualityImages,
101} from '#/state/preferences/high-quality-images'
102import {
103 useImageCdnHost,
104 useSetImageCdnHost,
105} from '#/state/preferences/image-cdn-host'
106import {useModerationOpts} from '#/state/preferences/moderation-opts'
107import {
108 useNoAppLabelers,
109 useSetNoAppLabelers,
110} from '#/state/preferences/no-app-labelers'
111import {
112 useNoDiscoverFallback,
113 useSetNoDiscoverFallback,
114} from '#/state/preferences/no-discover-fallback'
115import {
116 useOpenRouterApiKey,
117 useOpenRouterConfigured,
118 useOpenRouterModel,
119 useSetOpenRouterApiKey,
120 useSetOpenRouterModel,
121} from '#/state/preferences/openrouter'
122import {
123 usePdsLabelEnabled,
124 usePdsLabelHideBskyPds,
125 useSetPdsLabelEnabled,
126 useSetPdsLabelHideBskyPds,
127} from '#/state/preferences/pds-label'
128import {
129 usePostReplacement,
130 useSetPostReplacement,
131} from '#/state/preferences/post-name-replacement'
132import {
133 useRepostCarouselEnabled,
134 useSetRepostCarouselEnabled,
135} from '#/state/preferences/repost-carousel-enabled'
136import {
137 useSetShowLinkInHandle,
138 useShowLinkInHandle,
139} from '#/state/preferences/show-link-in-handle.tsx'
140import {
141 useLibreTranslateInstance,
142 useSetLibreTranslateInstance,
143 useSetTranslationServicePreference,
144 useTranslationServicePreference,
145} from '#/state/preferences/translation-service-preference'
146import {
147 useHandleInLinks,
148 useSetHandleInLinks,
149} from '#/state/preferences/use-handle-in-links'
150import {useProfilesQuery} from '#/state/queries/profile'
151import * as SettingsList from '#/screens/Settings/components/SettingsList'
152import {atoms as a, useBreakpoints} from '#/alf'
153import {Admonition} from '#/components/Admonition'
154import {Button, ButtonText} from '#/components/Button'
155import * as Dialog from '#/components/Dialog'
156import * as Toggle from '#/components/forms/Toggle'
157import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
158import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
159import {Eye_Stroke2_Corner0_Rounded as VisibilityIcon} from '#/components/icons/Eye'
160import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe'
161import {Lab_Stroke2_Corner0_Rounded as _BeakerIcon} from '#/components/icons/Lab'
162import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller'
163import {Pencil_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil'
164import {RaisingHand4Finger_Stroke2_Corner0_Rounded as RaisingHandIcon} from '#/components/icons/RaisingHand'
165import {Star_Stroke2_Corner0_Rounded as StarIcon} from '#/components/icons/Star'
166import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified'
167import * as Layout from '#/components/Layout'
168import {InlineLinkText} from '#/components/Link'
169import {Text} from '#/components/Typography'
170import {IS_WEB} from '#/env'
171import {SearchProfileCard} from '../Search/components/SearchProfileCard'
172
173type Props = NativeStackScreenProps<CommonNavigatorParams>
174
175function ConstellationInstanceDialog({
176 control,
177}: {
178 control: Dialog.DialogControlProps
179}) {
180 const pal = usePalette('default')
181 const {_} = useLingui()
182
183 const constellationInstance = useConstellationInstance()
184 const [url, setUrl] = useState(constellationInstance ?? '')
185 const setConstellationInstance = useSetConstellationInstance()
186
187 const submit = () => {
188 setConstellationInstance(url)
189 control.close()
190 }
191
192 const shouldDisable = () => {
193 try {
194 return !new URL(url).hostname.includes('.')
195 } catch (e) {
196 return true
197 }
198 }
199
200 return (
201 <Dialog.Outer
202 control={control}
203 nativeOptions={{preventExpansion: true}}
204 onClose={() => setUrl(constellationInstance ?? '')}>
205 <Dialog.Handle />
206 <Dialog.ScrollableInner label={_(msg`Constellations instance URL`)}>
207 <View style={[a.gap_sm, a.pb_lg]}>
208 <Text style={[a.text_2xl, a.font_bold]}>
209 <Trans>Constellations instance URL</Trans>
210 </Text>
211 </View>
212
213 <View style={a.gap_lg}>
214 <Dialog.Input
215 label="Text input field"
216 autoFocus
217 style={[styles.textInput, pal.border, pal.text]}
218 onChangeText={value => {
219 setUrl(value)
220 }}
221 placeholder={persisted.defaults.constellationInstance}
222 placeholderTextColor={pal.colors.textLight}
223 onSubmitEditing={submit}
224 accessibilityHint={_(
225 msg`Input the url of the constellations instance to use`,
226 )}
227 defaultValue={constellationInstance}
228 />
229
230 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
231 <Button
232 label={_(msg`Save`)}
233 size="large"
234 onPress={submit}
235 variant="solid"
236 color="primary"
237 disabled={shouldDisable()}>
238 <ButtonText>
239 <Trans>Save</Trans>
240 </ButtonText>
241 </Button>
242 </View>
243 </View>
244
245 <Dialog.Close />
246 </Dialog.ScrollableInner>
247 </Dialog.Outer>
248 )
249}
250
251function LibreTranslateInstanceDialog({
252 control,
253}: {
254 control: Dialog.DialogControlProps
255}) {
256 const pal = usePalette('default')
257 const {_} = useLingui()
258
259 const libreTranslateInstance = useLibreTranslateInstance()
260 const [url, setUrl] = useState(libreTranslateInstance ?? '')
261 const setLibreTranslateInstance = useSetLibreTranslateInstance()
262
263 const submit = () => {
264 setLibreTranslateInstance(url)
265 control.close()
266 }
267
268 const shouldDisable = () => {
269 try {
270 return !new URL(url).hostname.includes('.')
271 } catch (e) {
272 return true
273 }
274 }
275
276 return (
277 <Dialog.Outer
278 control={control}
279 nativeOptions={{preventExpansion: true}}
280 onClose={() => setUrl(libreTranslateInstance ?? '')}>
281 <Dialog.Handle />
282 <Dialog.ScrollableInner label={_(msg`LibreTranslate instance URL`)}>
283 <View style={[a.gap_sm, a.pb_lg]}>
284 <Text style={[a.text_2xl, a.font_bold]}>
285 <Trans>LibreTranslate instance URL</Trans>
286 </Text>
287 </View>
288
289 <View style={a.gap_lg}>
290 <Dialog.Input
291 label="Text input field"
292 autoFocus
293 style={[styles.textInput, pal.border, pal.text]}
294 onChangeText={value => {
295 setUrl(value)
296 }}
297 placeholder={persisted.defaults.libreTranslateInstance}
298 placeholderTextColor={pal.colors.textLight}
299 onSubmitEditing={submit}
300 accessibilityHint={_(
301 msg`Input the url of the LibreTranslate instance to use`,
302 )}
303 defaultValue={libreTranslateInstance}
304 />
305
306 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
307 <Button
308 label={_(msg`Save`)}
309 size="large"
310 onPress={submit}
311 variant="solid"
312 color="primary"
313 disabled={shouldDisable()}>
314 <ButtonText>
315 <Trans>Save</Trans>
316 </ButtonText>
317 </Button>
318 </View>
319 </View>
320
321 <Dialog.Close />
322 </Dialog.ScrollableInner>
323 </Dialog.Outer>
324 )
325}
326
327function ImageCdnHostDialog({control}: {control: Dialog.DialogControlProps}) {
328 const pal = usePalette('default')
329 const {_} = useLingui()
330
331 const imageCdnHost = useImageCdnHost()
332 const [url, setUrl] = useState(imageCdnHost ?? '')
333 const setImageCdnHost = useSetImageCdnHost()
334
335 const submit = () => {
336 try {
337 setImageCdnHost(new URL(url).origin)
338 } catch {
339 setImageCdnHost(url)
340 }
341 control.close()
342 }
343
344 const shouldDisable = () => {
345 try {
346 return !new URL(url).hostname.includes('.')
347 } catch (e) {
348 return true
349 }
350 }
351
352 return (
353 <Dialog.Outer
354 control={control}
355 nativeOptions={{preventExpansion: true}}
356 onClose={() => setUrl(imageCdnHost ?? '')}>
357 <Dialog.Handle />
358 <Dialog.ScrollableInner label={_(msg`Image CDN URL`)}>
359 <View style={[a.gap_sm, a.pb_lg]}>
360 <Text style={[a.text_2xl, a.font_bold]}>
361 <Trans>Image CDN URL</Trans>
362 </Text>
363 </View>
364
365 <View style={a.gap_lg}>
366 <Dialog.Input
367 label="Text input field"
368 autoFocus
369 style={[styles.textInput, pal.border, pal.text]}
370 onChangeText={value => {
371 setUrl(value)
372 }}
373 placeholder={persisted.defaults.imageCdnHost}
374 placeholderTextColor={pal.colors.textLight}
375 onSubmitEditing={submit}
376 accessibilityHint={_(msg`Input the URL of the image CDN to use`)}
377 defaultValue={imageCdnHost}
378 />
379
380 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
381 <Button
382 label={_(msg`Save`)}
383 size="large"
384 onPress={submit}
385 variant="solid"
386 color="primary"
387 disabled={shouldDisable()}>
388 <ButtonText>
389 <Trans>Save</Trans>
390 </ButtonText>
391 </Button>
392 </View>
393 </View>
394
395 <Dialog.Close />
396 </Dialog.ScrollableInner>
397 </Dialog.Outer>
398 )
399}
400
401function PostReplacementDialog({
402 control,
403}: {
404 control: Dialog.DialogControlProps
405}) {
406 const pal = usePalette('default')
407 const {_, i18n} = useLingui()
408
409 const postReplacement = usePostReplacement()
410 const setPostReplacement = useSetPostReplacement()
411
412 const [singular, setSingular] = useState(postReplacement.postName)
413 const [plural, setPlural] = useState(postReplacement.postsName)
414 const [pluralManuallyEdited, setPluralManuallyEdited] = useState(false)
415
416 const submit = async () => {
417 setPostReplacement({
418 enabled: singular.trim().toLowerCase() !== 'post',
419 postName: singular,
420 postsName: plural,
421 })
422
423 // Force reload the i18n messages to apply the replacement immediately
424 const locale = i18n.locale
425 await (IS_WEB
426 ? dynamicActivateWeb(locale as AppLanguage)
427 : dynamicActivate(locale as AppLanguage))
428
429 control.close()
430 }
431
432 const handleSingularChange = (value: string) => {
433 setSingular(value)
434 if (!pluralManuallyEdited) {
435 setPlural(value + 's')
436 }
437 }
438
439 const handlePluralChange = (value: string) => {
440 setPlural(value)
441 setPluralManuallyEdited(true)
442 }
443
444 const handlePresetSelect = (singularForm: string, pluralForm: string) => {
445 setSingular(singularForm)
446 setPlural(pluralForm)
447 setPluralManuallyEdited(false)
448 }
449
450 const shouldDisable = () => {
451 return !singular.trim() || !plural.trim()
452 }
453
454 return (
455 <Dialog.Outer
456 control={control}
457 nativeOptions={{preventExpansion: true}}
458 onClose={() => {
459 setSingular(postReplacement.postName)
460 setPlural(postReplacement.postsName)
461 setPluralManuallyEdited(false)
462 }}>
463 <Dialog.Handle />
464 <Dialog.ScrollableInner label={_(msg`Custom post phrase`)}>
465 <View style={[a.gap_sm, a.pb_lg]}>
466 <Text style={[a.text_2xl, a.font_bold]}>
467 <Trans>Custom post phrase</Trans>
468 </Text>
469 </View>
470
471 <View style={a.gap_lg}>
472 <Dialog.Input
473 label="Singular form"
474 autoFocus
475 style={[styles.textInput, pal.border, pal.text]}
476 onChangeText={handleSingularChange}
477 placeholder="skeet"
478 placeholderTextColor={pal.colors.textLight}
479 accessibilityHint={_(msg`Input the singular form (e.g., "skeet")`)}
480 value={singular}
481 />
482
483 <View style={[a.flex_row, a.flex_wrap, a.mb_xs]}>
484 {[
485 {singular: 'post', plural: 'posts'},
486 {singular: 'skeet', plural: 'skeets'},
487 {singular: 'note', plural: 'notes'},
488 {singular: 'woot', plural: 'woots'},
489 {singular: 'toot', plural: 'toots'},
490 {singular: 'silly', plural: 'sillies'},
491 ].map(preset => (
492 <Button
493 key={preset.singular}
494 variant="ghost"
495 color="primary"
496 label={preset.singular}
497 style={[a.px_sm, a.py_xs, a.rounded_sm, a.gap_sm]}
498 onPress={() =>
499 handlePresetSelect(preset.singular, preset.plural)
500 }>
501 <ButtonText>{preset.singular}</ButtonText>
502 </Button>
503 ))}
504 </View>
505
506 <Dialog.Input
507 label="Plural form"
508 style={[styles.textInput, pal.border, pal.text]}
509 onChangeText={handlePluralChange}
510 placeholder="skeets"
511 placeholderTextColor={pal.colors.textLight}
512 accessibilityHint={_(msg`Input the plural form (e.g., "skeets")`)}
513 value={plural}
514 />
515
516 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
517 <Button
518 label={_(msg`Save`)}
519 size="large"
520 onPress={submit}
521 variant="solid"
522 color="primary"
523 disabled={shouldDisable()}>
524 <ButtonText>
525 <Trans>Save</Trans>
526 </ButtonText>
527 </Button>
528 </View>
529 </View>
530
531 <Dialog.Close />
532 </Dialog.ScrollableInner>
533 </Dialog.Outer>
534 )
535}
536
537function TrustedVerifiersDialog({
538 control,
539}: {
540 control: Dialog.DialogControlProps
541}) {
542 const {_} = useLingui()
543
544 return (
545 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
546 <Dialog.Handle />
547 <Dialog.ScrollableInner label={_(msg`Trusted Verifiers`)}>
548 <View style={[a.gap_sm, a.pb_lg]}>
549 <Text style={[a.text_2xl, a.font_bold]}>
550 <Trans>Trusted Verifiers</Trans>
551 </Text>
552 </View>
553
554 <TrustedVerifiers />
555
556 <Dialog.Close />
557 </Dialog.ScrollableInner>
558 </Dialog.Outer>
559 )
560}
561
562const TrustedVerifiers = (): React.ReactNode => {
563 const trusted = useDeerVerificationTrusted()
564 const moderationOpts = useModerationOpts()
565
566 const results = useProfilesQuery({
567 handles: Array.from(trusted),
568 })
569
570 const {gtMobile} = useBreakpoints()
571
572 return (
573 results.data &&
574 moderationOpts !== undefined && (
575 <View style={[gtMobile ? a.pl_md : a.pl_sm, a.pb_sm]}>
576 {results.data.profiles.map(profile => (
577 <SearchProfileCard
578 key={profile.did}
579 profile={profile as ProfileViewBasic}
580 moderationOpts={moderationOpts}
581 />
582 ))}
583 </View>
584 )
585 )
586}
587
588function OpenRouterApiKeyDialog({
589 control,
590}: {
591 control: Dialog.DialogControlProps
592}) {
593 const pal = usePalette('default')
594 const {_} = useLingui()
595
596 const apiKey = useOpenRouterApiKey()
597 const [value, setValue] = useState(apiKey ?? '')
598 const setApiKey = useSetOpenRouterApiKey()
599
600 const submit = () => {
601 setApiKey(value.trim() || undefined)
602 control.close()
603 }
604
605 return (
606 <Dialog.Outer
607 control={control}
608 nativeOptions={{preventExpansion: true}}
609 onClose={() => setValue(apiKey ?? '')}>
610 <Dialog.Handle />
611 <Dialog.ScrollableInner label={_(msg`OpenRouter API Key`)}>
612 <View style={[a.gap_sm, a.pb_lg]}>
613 <Text style={[a.text_2xl, a.font_bold]}>
614 <Trans>OpenRouter API Key</Trans>
615 </Text>
616 </View>
617
618 <View style={a.gap_lg}>
619 <Dialog.Input
620 label="API Key"
621 autoFocus
622 style={[styles.textInput, pal.border, pal.text]}
623 onChangeText={setValue}
624 placeholder="sk-or-..."
625 placeholderTextColor={pal.colors.textLight}
626 onSubmitEditing={submit}
627 accessibilityHint={_(
628 msg`Enter your OpenRouter API key for AI alt text generation`,
629 )}
630 defaultValue={apiKey ?? ''}
631 secureTextEntry
632 />
633
634 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
635 <Button
636 label={_(msg`Save`)}
637 size="large"
638 onPress={submit}
639 variant="solid"
640 color="primary">
641 <ButtonText>
642 <Trans>Save</Trans>
643 </ButtonText>
644 </Button>
645 </View>
646 </View>
647
648 <Dialog.Close />
649 </Dialog.ScrollableInner>
650 </Dialog.Outer>
651 )
652}
653
654function OpenRouterModelDialog({
655 control,
656}: {
657 control: Dialog.DialogControlProps
658}) {
659 const pal = usePalette('default')
660 const {_} = useLingui()
661
662 const model = useOpenRouterModel()
663 const [value, setValue] = useState(model ?? '')
664 const setModel = useSetOpenRouterModel()
665
666 const submit = () => {
667 setModel(value.trim() || undefined)
668 control.close()
669 }
670
671 return (
672 <Dialog.Outer
673 control={control}
674 nativeOptions={{preventExpansion: true}}
675 onClose={() => setValue(model ?? '')}>
676 <Dialog.Handle />
677 <Dialog.ScrollableInner label={_(msg`OpenRouter Model`)}>
678 <View style={[a.gap_sm, a.pb_lg]}>
679 <Text style={[a.text_2xl, a.font_bold]}>
680 <Trans>OpenRouter Model</Trans>
681 </Text>
682 </View>
683
684 <View style={a.gap_lg}>
685 <Dialog.Input
686 label="Model"
687 autoFocus
688 style={[styles.textInput, pal.border, pal.text]}
689 onChangeText={setValue}
690 placeholder={DEFAULT_ALT_TEXT_AI_MODEL}
691 placeholderTextColor={pal.colors.textLight}
692 onSubmitEditing={submit}
693 accessibilityHint={_(
694 msg`Enter the model ID to use for alt text generation`,
695 )}
696 defaultValue={model ?? ''}
697 />
698
699 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
700 <Button
701 label={_(msg`Save`)}
702 size="large"
703 onPress={submit}
704 variant="solid"
705 color="primary">
706 <ButtonText>
707 <Trans>Save</Trans>
708 </ButtonText>
709 </Button>
710 </View>
711 </View>
712
713 <Dialog.Close />
714 </Dialog.ScrollableInner>
715 </Dialog.Outer>
716 )
717}
718
719export function RunesSettingsScreen({}: Props) {
720 const {_} = useLingui()
721
722 const goLinksEnabled = useGoLinksEnabled()
723 const setGoLinksEnabled = useSetGoLinksEnabled()
724
725 const directFetchRecords = useDirectFetchRecords()
726 const setDirectFetchRecords = useSetDirectFetchRecords()
727
728 const showExternalShareButtons = useShowExternalShareButtons()
729 const setShowExternalShareButtons = useSetShowExternalShareButtons()
730
731 const noAppLabelers = useNoAppLabelers()
732 const setNoAppLabelers = useSetNoAppLabelers()
733
734 const noDiscoverFallback = useNoDiscoverFallback()
735 const setNoDiscoverFallback = useSetNoDiscoverFallback()
736
737 const highQualityImages = useHighQualityImages()
738 const setHighQualityImages = useSetHighQualityImages()
739 const imageCdnHost = useImageCdnHost()
740
741 const hideFeedsPromoTab = useHideFeedsPromoTab()
742 const setHideFeedsPromoTab = useSetHideFeedsPromoTab()
743
744 const disableViaRepostNotification = useDisableViaRepostNotification()
745 const setDisableViaRepostNotification = useSetDisableViaRepostNotification()
746
747 const disableComposerPrompt = useDisableComposerPrompt()
748 const setDisableComposerPrompt = useSetDisableComposerPrompt()
749
750 const discoverContextEnabled = useDiscoverContextEnabled()
751 const setDiscoverContextEnabled = useSetDiscoverContextEnabled()
752
753 const disableLikesMetrics = useDisableLikesMetrics()
754 const setDisableLikesMetrics = useSetDisableLikesMetrics()
755
756 const disableRepostsMetrics = useDisableRepostsMetrics()
757 const setDisableRepostsMetrics = useSetDisableRepostsMetrics()
758
759 const disableQuotesMetrics = useDisableQuotesMetrics()
760 const setDisableQuotesMetrics = useSetDisableQuotesMetrics()
761
762 const disableSavesMetrics = useDisableSavesMetrics()
763 const setDisableSavesMetrics = useSetDisableSavesMetrics()
764
765 const disableReplyMetrics = useDisableReplyMetrics()
766 const setDisableReplyMetrics = useSetDisableReplyMetrics()
767
768 const disableFollowersMetrics = useDisableFollowersMetrics()
769 const setDisableFollowersMetrics = useSetDisableFollowersMetrics()
770
771 const disableFollowingMetrics = useDisableFollowingMetrics()
772 const setDisableFollowingMetrics = useSetDisableFollowingMetrics()
773
774 const disableFollowedByMetrics = useDisableFollowedByMetrics()
775 const setDisableFollowedByMetrics = useSetDisableFollowedByMetrics()
776
777 const disablePostsMetrics = useDisablePostsMetrics()
778 const setDisablePostsMetrics = useSetDisablePostsMetrics()
779
780 const hideSimilarAccountsRecomm = useHideSimilarAccountsRecomm()
781 const setHideSimilarAccountsRecomm = useSetHideSimilarAccountsRecomm()
782
783 const hideUnreplyablePosts = useHideUnreplyablePosts()
784 const setHideUnreplyablePosts = useSetHideUnreplyablePosts()
785
786 const disableVerifyEmailReminder = useDisableVerifyEmailReminder()
787 const setDisableVerifyEmailReminder = useSetDisableVerifyEmailReminder()
788
789 const constellationInstance = useConstellationInstance()
790 const setConstellationInstanceControl = Dialog.useDialogControl()
791
792 const setTrustedVerifiersDialogControl = Dialog.useDialogControl()
793
794 const deerVerificationEnabled = useDeerVerificationEnabled()
795 const setDeerVerificationEnabled = useSetDeerVerificationEnabled()
796
797 const pdsLabelEnabled = usePdsLabelEnabled()
798 const setPdsLabelEnabled = useSetPdsLabelEnabled()
799 const pdsLabelHideBskyPds = usePdsLabelHideBskyPds()
800 const setPdsLabelHideBskyPds = useSetPdsLabelHideBskyPds()
801
802 const repostCarouselEnabled = useRepostCarouselEnabled()
803 const setRepostCarouselEnabled = useSetRepostCarouselEnabled()
804
805 const showLinkInHandle = useShowLinkInHandle()
806 const setShowLinkInHandle = useSetShowLinkInHandle()
807
808 const handleInLinks = useHandleInLinks()
809 const setHandleInLinks = useSetHandleInLinks()
810
811 const translationServicePreference = useTranslationServicePreference()
812 const setTranslationServicePreference = useSetTranslationServicePreference()
813
814 const setLibreTranslateInstanceControl = Dialog.useDialogControl()
815
816 const setImageCdnHostControl = Dialog.useDialogControl()
817
818 const setPostReplacementDialogControl = Dialog.useDialogControl()
819
820 const setOpenRouterApiKeyControl = Dialog.useDialogControl()
821 const openRouterModel = useOpenRouterModel()
822 const setOpenRouterModelControl = Dialog.useDialogControl()
823 const openRouterConfigured = useOpenRouterConfigured()
824
825 return (
826 <Layout.Screen>
827 <Layout.Header.Outer>
828 <Layout.Header.BackButton />
829 <Layout.Header.Content>
830 <Layout.Header.TitleText>
831 <Trans>Runes</Trans>
832 </Layout.Header.TitleText>
833 </Layout.Header.Content>
834 <Layout.Header.Slot />
835 </Layout.Header.Outer>
836 <Layout.Content>
837 <SettingsList.Container>
838 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
839 <SettingsList.ItemIcon icon={AtomIcon} />
840 <SettingsList.ItemText>
841 <Trans>Redirects</Trans>
842 </SettingsList.ItemText>
843 <Toggle.Item
844 name="use_go_links"
845 label={_(msg`Redirect through go.bsky.app`)}
846 value={goLinksEnabled ?? false}
847 onChange={value => setGoLinksEnabled(value)}
848 style={[a.w_full]}>
849 <Toggle.LabelText style={[a.flex_1]}>
850 <Trans>Redirect through go.bsky.app</Trans>
851 </Toggle.LabelText>
852 <Toggle.Platform />
853 </Toggle.Item>
854 <Toggle.Item
855 name="use_handle_in_links"
856 label={_(
857 msg`Use handles in profile links instead of DIDs (requires restart)`,
858 )}
859 value={handleInLinks ?? false}
860 onChange={value => setHandleInLinks(value)}
861 style={[a.w_full]}>
862 <Toggle.LabelText style={[a.flex_1]}>
863 <Trans>Use handles in profile links instead of DIDs</Trans>
864 </Toggle.LabelText>
865 <Toggle.Platform />
866 </Toggle.Item>
867 </SettingsList.Group>
868
869 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
870 <SettingsList.ItemIcon icon={VisibilityIcon} />
871 <SettingsList.ItemText>
872 <Trans>Visibility</Trans>
873 </SettingsList.ItemText>
874 <Toggle.Item
875 name="direct_fetch_records"
876 label={_(
877 msg`Fetch records directly from PDS to see through quote blocks`,
878 )}
879 value={directFetchRecords}
880 onChange={value => setDirectFetchRecords(value)}
881 style={[a.w_full]}>
882 <Toggle.LabelText style={[a.flex_1]}>
883 <Trans>
884 Fetch records directly from PDS to see contents of blocked and
885 detached quotes
886 </Trans>
887 </Toggle.LabelText>
888 <Toggle.Platform />
889 </Toggle.Item>
890 </SettingsList.Group>
891
892 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
893 <SettingsList.ItemIcon icon={ChainLinkIcon} />
894 <SettingsList.ItemText>
895 <Trans>Bridging and Fediverse</Trans>
896 </SettingsList.ItemText>
897 <Toggle.Item
898 name="external_share_buttons"
899 label={_(
900 msg`Show "Open original post" and "Open post in PDSls" buttons`,
901 )}
902 value={showExternalShareButtons}
903 onChange={value => setShowExternalShareButtons(value)}
904 style={[a.w_full]}>
905 <Toggle.LabelText style={[a.flex_1]}>
906 <Trans>
907 Show "Open original post" and "Open post in PDSls" buttons
908 </Trans>
909 </Toggle.LabelText>
910 <Toggle.Platform />
911 </Toggle.Item>
912 </SettingsList.Group>
913
914 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
915 <SettingsList.ItemIcon icon={VerifiedIcon} />
916 <SettingsList.ItemText>
917 <Trans>Verification</Trans>
918 </SettingsList.ItemText>
919 <Toggle.Item
920 name="custom_verifications"
921 label={_(
922 msg`Select your own set of trusted verifiers, and operate as a verifier`,
923 )}
924 value={deerVerificationEnabled}
925 onChange={value => setDeerVerificationEnabled(value)}
926 style={[a.w_full]}>
927 <Toggle.LabelText style={[a.flex_1]}>
928 <Trans>
929 Select your own set of trusted verifiers, and operate as a
930 verifier
931 </Trans>
932 </Toggle.LabelText>
933 <Toggle.Platform />
934 </Toggle.Item>
935 </SettingsList.Group>
936
937 <SettingsList.Item>
938 <Admonition type="warning" style={[a.flex_1]}>
939 <Trans>
940 May slow down the client or fail to find all labels. Revoke and
941 grant trust in the meatball menu on a profile.{' '}
942 {deerVerificationEnabled
943 ? 'You currently'
944 : 'If enabled, you would'}{' '}
945 trust the following verifiers:
946 </Trans>
947 </Admonition>
948 </SettingsList.Item>
949
950 <SettingsList.Item>
951 <SettingsList.ItemIcon icon={VerifiedIcon} />
952 <SettingsList.ItemText>
953 <Trans>{`Trusted Verifiers`}</Trans>
954 </SettingsList.ItemText>
955 <SettingsList.BadgeButton
956 label={_(msg`View`)}
957 onPress={() => setTrustedVerifiersDialogControl.open()}
958 />
959 </SettingsList.Item>
960
961 <SettingsList.Item>
962 <SettingsList.ItemIcon icon={StarIcon} />
963 <SettingsList.ItemText>
964 <Trans>{`Constellation Instance`}</Trans>
965 </SettingsList.ItemText>
966 <SettingsList.BadgeButton
967 label={_(msg`Change`)}
968 onPress={() => setConstellationInstanceControl.open()}
969 />
970 </SettingsList.Item>
971 <SettingsList.Item>
972 <Admonition type="info" style={[a.flex_1]}>
973 <Trans>
974 Constellation is used to supplement AppView responses for custom
975 verifications and nuclear block bypass, via backlinks. Current
976 instance:\u00A0
977 <InlineLinkText
978 to={constellationInstance}
979 label={constellationInstance}>
980 {constellationInstance}
981 </InlineLinkText>
982 </Trans>
983 </Admonition>
984 </SettingsList.Item>
985
986 <SettingsList.Divider />
987
988 <SettingsList.Item>
989 <SettingsList.ItemIcon icon={PencilIcon} />
990 <SettingsList.ItemText>
991 <Trans>{`Custom post phrase`}</Trans>
992 </SettingsList.ItemText>
993 <SettingsList.BadgeButton
994 label={_(msg`Change`)}
995 onPress={() => setPostReplacementDialogControl.open()}
996 />
997 </SettingsList.Item>
998
999 <SettingsList.Divider />
1000
1001 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
1002 <SettingsList.ItemIcon icon={PaintRollerIcon} />
1003 <SettingsList.ItemText>
1004 <Trans>Tweaks</Trans>
1005 </SettingsList.ItemText>
1006 <Toggle.Item
1007 name="pds_label_badge"
1008 label={_(
1009 msg`Show a PDS badge next to the display name on profiles`,
1010 )}
1011 value={pdsLabelEnabled}
1012 onChange={value => setPdsLabelEnabled(value)}
1013 style={[a.w_full]}>
1014 <Toggle.LabelText style={[a.flex_1]}>
1015 <Trans>
1016 Show a PDS badge next to the display name on profiles
1017 </Trans>
1018 </Toggle.LabelText>
1019 <Toggle.Platform />
1020 </Toggle.Item>
1021 {pdsLabelEnabled && (
1022 <Toggle.Item
1023 name="pds_label_hide_bsky"
1024 label={_(msg`Hide PDS badge for Bluesky-hosted accounts`)}
1025 value={pdsLabelHideBskyPds}
1026 onChange={value => setPdsLabelHideBskyPds(value)}
1027 style={[a.w_full]}>
1028 <Toggle.LabelText style={[a.flex_1]}>
1029 <Trans>Hide PDS badge for Bluesky-hosted accounts</Trans>
1030 </Toggle.LabelText>
1031 <Toggle.Platform />
1032 </Toggle.Item>
1033 )}
1034
1035 <Toggle.Item
1036 name="repost_carousel"
1037 label={_(msg`Combine reposts into a horizontal carousel`)}
1038 value={repostCarouselEnabled}
1039 onChange={value => setRepostCarouselEnabled(value)}
1040 style={[a.w_full]}>
1041 <Toggle.LabelText style={[a.flex_1]}>
1042 <Trans>Combine reposts into a horizontal carousel</Trans>
1043 </Toggle.LabelText>
1044 <Toggle.Platform />
1045 </Toggle.Item>
1046
1047 <Toggle.Item
1048 name="show_link_in_handle"
1049 label={_(
1050 msg`On non-bsky.social handles, show a link to that URL`,
1051 )}
1052 value={showLinkInHandle}
1053 onChange={value => setShowLinkInHandle(value)}
1054 style={[a.w_full]}>
1055 <Toggle.LabelText style={[a.flex_1]}>
1056 <Trans>
1057 On non-bsky.social handles, show a link to that URL
1058 </Trans>
1059 </Toggle.LabelText>
1060 <Toggle.Platform />
1061 </Toggle.Item>
1062
1063 <Toggle.Item
1064 name="no_discover_fallback"
1065 label={_(msg`Do not fall back to discover feed`)}
1066 value={noDiscoverFallback}
1067 onChange={value => setNoDiscoverFallback(value)}
1068 style={[a.w_full]}>
1069 <Toggle.LabelText style={[a.flex_1]}>
1070 <Trans>Do not fall back to discover feed</Trans>
1071 </Toggle.LabelText>
1072 <Toggle.Platform />
1073 </Toggle.Item>
1074
1075 <Toggle.Item
1076 name="high_quality_images"
1077 label={_(msg`Display images in higher quality`)}
1078 value={highQualityImages}
1079 onChange={value => setHighQualityImages(value)}
1080 style={[a.w_full]}>
1081 <Toggle.LabelText style={[a.flex_1]}>
1082 <Trans>Display images in higher quality</Trans>
1083 </Toggle.LabelText>
1084 <Toggle.Platform />
1085 </Toggle.Item>
1086 <Admonition type="info" style={[a.flex_1]}>
1087 <Trans>
1088 Images will be served as PNG instead of JPEG. Images will take
1089 longer to load and use more bandwidth.
1090 </Trans>
1091 </Admonition>
1092 </SettingsList.Group>
1093
1094 <SettingsList.Item>
1095 <SettingsList.ItemIcon icon={EarthIcon} />
1096 <SettingsList.ItemText>
1097 <Trans>{`Image CDN`}</Trans>
1098 </SettingsList.ItemText>
1099 <SettingsList.BadgeButton
1100 label={_(msg`Change`)}
1101 onPress={() => setImageCdnHostControl.open()}
1102 />
1103 </SettingsList.Item>
1104 <SettingsList.Item>
1105 <Admonition type="info" style={[a.flex_1]}>
1106 <Trans>
1107 Override the CDN host for all images. Current:
1108 <InlineLinkText to={imageCdnHost} label={imageCdnHost}>
1109 {imageCdnHost}
1110 </InlineLinkText>
1111 </Trans>
1112 </Admonition>
1113 </SettingsList.Item>
1114
1115 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
1116 <Toggle.Item
1117 name="hide_feeds_promo_tab"
1118 label={_(msg`Hide "Feeds ✨" tab when only one feed is selected`)}
1119 value={hideFeedsPromoTab}
1120 onChange={value => setHideFeedsPromoTab(value)}
1121 style={[a.w_full]}>
1122 <Toggle.LabelText style={[a.flex_1]}>
1123 <Trans>
1124 Hide "Feeds ✨" tab when only one feed is selected
1125 </Trans>
1126 </Toggle.LabelText>
1127 <Toggle.Platform />
1128 </Toggle.Item>
1129
1130 <Toggle.Item
1131 name="disable_via_repost_notification"
1132 label={_(msg`Disable via repost notifications`)}
1133 value={disableViaRepostNotification}
1134 onChange={value => setDisableViaRepostNotification(value)}
1135 style={[a.w_full]}>
1136 <Toggle.LabelText style={[a.flex_1]}>
1137 <Trans>Disable via repost notifications</Trans>
1138 </Toggle.LabelText>
1139 <Toggle.Platform />
1140 </Toggle.Item>
1141 <Admonition type="info" style={[a.flex_1]}>
1142 <Trans>
1143 Forcefully disables the notifications other people receive when
1144 you like/repost a post someone else has reposted for privacy.
1145 </Trans>
1146 </Admonition>
1147
1148 <Toggle.Item
1149 name="hide_similar_accounts_recommendations"
1150 label={_(msg`Hide similar accounts recommendations`)}
1151 value={hideSimilarAccountsRecomm}
1152 onChange={value => setHideSimilarAccountsRecomm(value)}
1153 style={[a.w_full]}>
1154 <Toggle.LabelText style={[a.flex_1]}>
1155 <Trans>Hide similar accounts recommendations</Trans>
1156 </Toggle.LabelText>
1157 <Toggle.Platform />
1158 </Toggle.Item>
1159
1160 <Toggle.Item
1161 name="hide_unreplyable_posts"
1162 label={_(msg`Hide posts that cannot be replied to from feeds`)}
1163 value={hideUnreplyablePosts}
1164 onChange={value => setHideUnreplyablePosts(value)}
1165 style={[a.w_full]}>
1166 <Toggle.LabelText style={[a.flex_1]}>
1167 <Trans>Hide posts that cannot be replied to from feeds</Trans>
1168 </Toggle.LabelText>
1169 <Toggle.Platform />
1170 </Toggle.Item>
1171 <Admonition type="info" style={[a.flex_1]}>
1172 <Trans>
1173 Hides posts from feeds where replies are disabled (e.g. due to
1174 postgates or other restrictions). Does not affect thread views.
1175 </Trans>
1176 </Admonition>
1177
1178 <Toggle.Item
1179 name="disable_composer_prompt"
1180 label={_(msg`Disable composer prompt`)}
1181 value={disableComposerPrompt}
1182 onChange={value => setDisableComposerPrompt(value)}
1183 style={[a.w_full]}>
1184 <Toggle.LabelText style={[a.flex_1]}>
1185 <Trans>Disable composer prompt</Trans>
1186 </Toggle.LabelText>
1187 <Toggle.Platform />
1188 </Toggle.Item>
1189
1190 <Toggle.Item
1191 name="disable_verify_email_reminder"
1192 label={_(msg`Disable verify email reminder`)}
1193 value={disableVerifyEmailReminder}
1194 onChange={value => setDisableVerifyEmailReminder(value)}
1195 style={[a.w_full]}>
1196 <Toggle.LabelText style={[a.flex_1]}>
1197 <Trans>Disable verify email reminder</Trans>
1198 </Toggle.LabelText>
1199 <Toggle.Platform />
1200 </Toggle.Item>
1201 <Admonition type="warning" style={[a.flex_1]}>
1202 <Trans>
1203 This only gets rid of the reminder on app launch, useful if your
1204 PDS does not have email verification setup.\u00A0 This does NOT
1205 give access to features locked behind email verification.
1206 </Trans>
1207 </Admonition>
1208
1209 <Toggle.Item
1210 name="discover_context"
1211 label={_(msg`Show debug context for posts in Discover feed`)}
1212 value={discoverContextEnabled}
1213 onChange={value => setDiscoverContextEnabled(value)}
1214 style={[a.w_full]}>
1215 <Toggle.LabelText style={[a.flex_1]}>
1216 <Trans>Show debug context for posts in Discover feed</Trans>
1217 </Toggle.LabelText>
1218 <Toggle.Platform />
1219 </Toggle.Item>
1220 </SettingsList.Group>
1221
1222 <SettingsList.Divider />
1223
1224 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
1225 <SettingsList.ItemIcon icon={EarthIcon} />
1226 <SettingsList.ItemText>
1227 <Trans>Post Translation Provider</Trans>
1228 </SettingsList.ItemText>
1229
1230 <Toggle.Item
1231 name="service_google"
1232 label={_(msg`Use Google Translate`)}
1233 value={translationServicePreference === 'google'}
1234 onChange={() => setTranslationServicePreference('google')}
1235 style={[a.w_full]}>
1236 <Toggle.LabelText style={[a.flex_1]}>
1237 <Trans>Use Google Translate</Trans>
1238 </Toggle.LabelText>
1239 <Toggle.Radio />
1240 </Toggle.Item>
1241
1242 <Toggle.Item
1243 name="service_kagi"
1244 label={_(msg`Use Kagi Translate`)}
1245 value={translationServicePreference === 'kagi'}
1246 onChange={() => setTranslationServicePreference('kagi')}
1247 style={[a.w_full]}>
1248 <Toggle.LabelText style={[a.flex_1]}>
1249 <Trans>Use Kagi Translate</Trans>
1250 </Toggle.LabelText>
1251 <Toggle.Radio />
1252 </Toggle.Item>
1253
1254 <Toggle.Item
1255 name="service_papago"
1256 label={_(msg`Use Naver Papago`)}
1257 value={translationServicePreference === 'papago'}
1258 onChange={() => setTranslationServicePreference('papago')}
1259 style={[a.w_full]}>
1260 <Toggle.LabelText style={[a.flex_1]}>
1261 <Trans>Use Naver Papago</Trans>
1262 </Toggle.LabelText>
1263 <Toggle.Radio />
1264 </Toggle.Item>
1265
1266 <Toggle.Item
1267 name="service_libreTranslate"
1268 label={_(msg`Use LibreTranslate`)}
1269 value={translationServicePreference === 'libreTranslate'}
1270 onChange={() => setTranslationServicePreference('libreTranslate')}
1271 style={[a.w_full]}>
1272 <Toggle.LabelText style={[a.flex_1]}>
1273 <Trans>Use LibreTranslate</Trans>
1274 </Toggle.LabelText>
1275 <Toggle.Radio />
1276 </Toggle.Item>
1277 </SettingsList.Group>
1278
1279 {translationServicePreference === 'libreTranslate' && (
1280 <SettingsList.Item>
1281 <SettingsList.ItemIcon icon={EarthIcon} />
1282 <SettingsList.ItemText>
1283 <Trans>{`LibreTranslate Instance`}</Trans>
1284 </SettingsList.ItemText>
1285 <SettingsList.BadgeButton
1286 label={_(msg`Change`)}
1287 onPress={() => setLibreTranslateInstanceControl.open()}
1288 />
1289 </SettingsList.Item>
1290 )}
1291
1292 <SettingsList.Divider />
1293
1294 <SettingsList.Item>
1295 <SettingsList.ItemIcon icon={_BeakerIcon} />
1296 <SettingsList.ItemText>
1297 <Trans>OpenRouter API Key</Trans>
1298 </SettingsList.ItemText>
1299 <SettingsList.BadgeButton
1300 label={openRouterConfigured ? _(msg`Change`) : _(msg`Set`)}
1301 onPress={() => setOpenRouterApiKeyControl.open()}
1302 />
1303 </SettingsList.Item>
1304
1305 <SettingsList.Item>
1306 <Admonition type="info" style={[a.flex_1]}>
1307 <Trans>
1308 Set your OpenRouter API key to enable AI-powered alt text
1309 generation for images in the composer. Get an API key at{' '}
1310 <InlineLinkText
1311 to="https://openrouter.ai"
1312 label="openrouter.ai">
1313 openrouter.ai
1314 </InlineLinkText>
1315 </Trans>
1316 </Admonition>
1317 </SettingsList.Item>
1318
1319 {openRouterConfigured && (
1320 <SettingsList.Item>
1321 <SettingsList.ItemIcon icon={_BeakerIcon} />
1322 <SettingsList.ItemText>
1323 <Trans>{`OpenRouter Model`}</Trans>
1324 </SettingsList.ItemText>
1325 <SettingsList.BadgeButton
1326 label={_(msg`Change`)}
1327 onPress={() => setOpenRouterModelControl.open()}
1328 />
1329 </SettingsList.Item>
1330 )}
1331
1332 {openRouterConfigured && (
1333 <SettingsList.Item>
1334 <Admonition type="info" style={[a.flex_1]}>
1335 <Trans>
1336 Current model: {openRouterModel ?? DEFAULT_ALT_TEXT_AI_MODEL}.{' '}
1337 <InlineLinkText
1338 to="https://openrouter.ai/models?fmt=cards&input_modalities=image&order=most-popular"
1339 label="openrouter.ai">
1340 Search models
1341 </InlineLinkText>
1342 </Trans>
1343 </Admonition>
1344 </SettingsList.Item>
1345 )}
1346
1347 <SettingsList.Divider />
1348
1349 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
1350 <SettingsList.ItemIcon icon={VisibilityIcon} />
1351 <SettingsList.ItemText>
1352 <Trans>Metrics</Trans>
1353 </SettingsList.ItemText>
1354
1355 <Toggle.Item
1356 name="disable_likes_metrics"
1357 label={_(msg`Disable likes metrics`)}
1358 value={disableLikesMetrics}
1359 onChange={value => setDisableLikesMetrics(value)}
1360 style={[a.w_full]}>
1361 <Toggle.LabelText style={[a.flex_1]}>
1362 <Trans>Disable likes metrics</Trans>
1363 </Toggle.LabelText>
1364 <Toggle.Platform />
1365 </Toggle.Item>
1366
1367 <Toggle.Item
1368 name="disable_reposts_metrics"
1369 label={_(msg`Disable reposts metrics`)}
1370 value={disableRepostsMetrics}
1371 onChange={value => setDisableRepostsMetrics(value)}
1372 style={[a.w_full]}>
1373 <Toggle.LabelText style={[a.flex_1]}>
1374 <Trans>Disable reposts metrics</Trans>
1375 </Toggle.LabelText>
1376 <Toggle.Platform />
1377 </Toggle.Item>
1378
1379 <Toggle.Item
1380 name="disable_quotes_metrics"
1381 label={_(msg`Disable quotes metrics`)}
1382 value={disableQuotesMetrics}
1383 onChange={value => setDisableQuotesMetrics(value)}
1384 style={[a.w_full]}>
1385 <Toggle.LabelText style={[a.flex_1]}>
1386 <Trans>Disable quotes metrics</Trans>
1387 </Toggle.LabelText>
1388 <Toggle.Platform />
1389 </Toggle.Item>
1390
1391 <Toggle.Item
1392 name="disable_saves_metrics"
1393 label={_(msg`Disable saves metrics`)}
1394 value={disableSavesMetrics}
1395 onChange={value => setDisableSavesMetrics(value)}
1396 style={[a.w_full]}>
1397 <Toggle.LabelText style={[a.flex_1]}>
1398 <Trans>Disable saves metrics</Trans>
1399 </Toggle.LabelText>
1400 <Toggle.Platform />
1401 </Toggle.Item>
1402
1403 <Toggle.Item
1404 name="disable_reply_metrics"
1405 label={_(msg`Disable reply metrics`)}
1406 value={disableReplyMetrics}
1407 onChange={value => setDisableReplyMetrics(value)}
1408 style={[a.w_full]}>
1409 <Toggle.LabelText style={[a.flex_1]}>
1410 <Trans>Disable reply metrics</Trans>
1411 </Toggle.LabelText>
1412 <Toggle.Platform />
1413 </Toggle.Item>
1414
1415 <Toggle.Item
1416 name="disable_followers_metrics"
1417 label={_(msg`Disable followers metrics`)}
1418 value={disableFollowersMetrics}
1419 onChange={value => setDisableFollowersMetrics(value)}
1420 style={[a.w_full]}>
1421 <Toggle.LabelText style={[a.flex_1]}>
1422 <Trans>Disable followers metrics</Trans>
1423 </Toggle.LabelText>
1424 <Toggle.Platform />
1425 </Toggle.Item>
1426
1427 <Toggle.Item
1428 name="disable_following_metrics"
1429 label={_(msg`Disable following metrics`)}
1430 value={disableFollowingMetrics}
1431 onChange={value => setDisableFollowingMetrics(value)}
1432 style={[a.w_full]}>
1433 <Toggle.LabelText style={[a.flex_1]}>
1434 <Trans>Disable following metrics</Trans>
1435 </Toggle.LabelText>
1436 <Toggle.Platform />
1437 </Toggle.Item>
1438
1439 <Toggle.Item
1440 name="disable_followed_by_metrics"
1441 label={_(msg`Disable "followed by" metrics`)}
1442 value={disableFollowedByMetrics}
1443 onChange={value => setDisableFollowedByMetrics(value)}
1444 style={[a.w_full]}>
1445 <Toggle.LabelText style={[a.flex_1]}>
1446 <Trans>Disable "followed by" metrics</Trans>
1447 </Toggle.LabelText>
1448 <Toggle.Platform />
1449 </Toggle.Item>
1450
1451 <Toggle.Item
1452 name="disable_posts_metrics"
1453 label={_(msg`Disable posts metrics`)}
1454 value={disablePostsMetrics}
1455 onChange={value => setDisablePostsMetrics(value)}
1456 style={[a.w_full]}>
1457 <Toggle.LabelText style={[a.flex_1]}>
1458 <Trans>Disable posts metrics</Trans>
1459 </Toggle.LabelText>
1460 <Toggle.Platform />
1461 </Toggle.Item>
1462 </SettingsList.Group>
1463
1464 <SettingsList.Divider />
1465
1466 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
1467 <SettingsList.ItemIcon icon={RaisingHandIcon} />
1468 <SettingsList.ItemText>
1469 <Trans>Labelers</Trans>
1470 </SettingsList.ItemText>
1471 <Toggle.Item
1472 name="no_app_labelers"
1473 label={_(msg`Do not declare any app labelers`)}
1474 value={noAppLabelers}
1475 onChange={value => setNoAppLabelers(value)}
1476 style={[a.w_full]}>
1477 <Toggle.LabelText style={[a.flex_1]}>
1478 <Trans>Do not declare any default app labelers</Trans>
1479 </Toggle.LabelText>
1480 <Toggle.Platform />
1481 </Toggle.Item>
1482 </SettingsList.Group>
1483
1484 <SettingsList.Item>
1485 <Admonition type="warning" style={[a.flex_1]}>
1486 <Trans>Restart the app after changing this setting.</Trans>
1487 </Admonition>
1488 </SettingsList.Item>
1489 <SettingsList.Item>
1490 <Admonition type="tip" style={[a.flex_1]}>
1491 <Trans>
1492 Some App Views will default to using an app labeler if you have
1493 no labelers, so consider subscribing to at least one labeler if
1494 you have issues.
1495 </Trans>
1496 </Admonition>
1497 </SettingsList.Item>
1498 <SettingsList.Item>
1499 <Admonition type="info" style={[a.flex_1]}>
1500 <Trans>
1501 App labelers are mandatory top-level labelers that can perform
1502 "takedowns". This setting does not influence geolocation-based
1503 labelers.
1504 </Trans>
1505 </Admonition>
1506 </SettingsList.Item>
1507 </SettingsList.Container>
1508 </Layout.Content>
1509 <ConstellationInstanceDialog control={setConstellationInstanceControl} />
1510 <TrustedVerifiersDialog control={setTrustedVerifiersDialogControl} />
1511 <LibreTranslateInstanceDialog
1512 control={setLibreTranslateInstanceControl}
1513 />
1514 <ImageCdnHostDialog control={setImageCdnHostControl} />
1515 <PostReplacementDialog control={setPostReplacementDialogControl} />
1516 <OpenRouterApiKeyDialog control={setOpenRouterApiKeyControl} />
1517 <OpenRouterModelDialog control={setOpenRouterModelControl} />
1518 </Layout.Screen>
1519 )
1520}
1521
1522const styles = {
1523 textInput: {
1524 borderWidth: 1,
1525 borderRadius: 6,
1526 paddingHorizontal: 14,
1527 paddingVertical: 10,
1528 fontSize: 16,
1529 },
1530}