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