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