Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 1530 lines 54 kB view raw
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}