import {useState} from 'react' import {LayoutAnimation, Pressable, View} from 'react-native' import {Linking} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {useActorStatus} from '#/lib/actor-status' import {IS_INTERNAL} from '#/lib/app-info' import {HELP_DESK_URL} from '#/lib/constants' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import { type CommonNavigatorParams, type NavigationProp, } from '#/lib/routes/types' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' import * as persisted from '#/state/persisted' import {clearStorage} from '#/state/persisted' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' import {type SessionAccount, useSession, useSessionApi} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' import * as Toast from '#/view/com/util/Toast' import {UserAvatar} from '#/view/com/util/UserAvatar' import * as SettingsList from '#/screens/Settings/components/SettingsList' import {atoms as a, platform, tokens, useBreakpoints, useTheme} from '#/alf' import {AgeAssuranceDismissibleNotice} from '#/components/ageAssurance/AgeAssuranceDismissibleNotice' import {AvatarStackWithFetch} from '#/components/AvatarStack' import {useDialogControl} from '#/components/Dialog' import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility' import {Bell_Stroke2_Corner0_Rounded as NotificationIcon} from '#/components/icons/Bell' import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' import {ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon} from '#/components/icons/Chevron' import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion' import {CodeBrackets_Stroke2_Corner2_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock' import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' import { Person_Stroke2_Corner2_Rounded as PersonIcon, PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon, PersonPlus_Stroke2_Corner2_Rounded as PersonPlusIcon, PersonX_Stroke2_Corner0_Rounded as PersonXIcon, } from '#/components/icons/Person' import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' import {useFullVerificationState} from '#/components/verification' import { shouldShowVerificationCheckButton, VerificationCheckButton, } from '#/components/verification/VerificationCheckButton' import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' type Props = NativeStackScreenProps export function SettingsScreen({}: Props) { const {_} = useLingui() const reducedMotion = useReducedMotion() const {logoutEveryAccount} = useSessionApi() const {accounts, currentAccount} = useSession() const switchAccountControl = useDialogControl() const signOutPromptControl = Prompt.usePromptControl() const {data: profile} = useProfileQuery({did: currentAccount?.did}) const {data: otherProfiles} = useProfilesQuery({ handles: accounts .filter(acc => acc.did !== currentAccount?.did) .map(acc => acc.handle), }) const {pendingDid, onPressSwitchAccount} = useAccountSwitcher() const [showAccounts, setShowAccounts] = useState(false) const [showDevOptions, setShowDevOptions] = useState(false) return ( Settings {profile && } {accounts.length > 1 ? ( <> { if (!reducedMotion) { LayoutAnimation.configureNext( LayoutAnimation.Presets.easeInEaseOut, ) } setShowAccounts(s => !s) }}> Switch account {showAccounts ? ( ) : ( acc.did) .filter(did => did !== currentAccount?.did) .slice(0, 5)} /> )} {showAccounts && ( <> {accounts .filter(acc => acc.did !== currentAccount?.did) .map(account => ( p.did === account.did, )} pendingDid={pendingDid} onPressSwitchAccount={onPressSwitchAccount} /> ))} )} ) : ( )} Account Privacy and security Moderation Notifications Content and media Appearance Accessibility Languages Linking.openURL(HELP_DESK_URL)} label={_(msg`Help`)} accessibilityHint={_(msg`Opens helpdesk in browser`)}> Help About signOutPromptControl.open()} label={_(msg`Sign out`)}> Sign out {IS_INTERNAL && ( <> { if (!reducedMotion) { LayoutAnimation.configureNext( LayoutAnimation.Presets.easeInEaseOut, ) } setShowDevOptions(d => !d) }} label={_(msg`Developer options`)}> Developer options {showDevOptions && } )} logoutEveryAccount('Settings')} confirmButtonCta={_(msg`Sign out`)} cancelButtonCta={_(msg`Cancel`)} confirmButtonColor="negative" /> ) } function ProfilePreview({ profile, }: { profile: AppBskyActorDefs.ProfileViewDetailed }) { const t = useTheme() const {gtMobile} = useBreakpoints() const shadow = useProfileShadow(profile) const moderationOpts = useModerationOpts() const verificationState = useFullVerificationState({ profile: shadow, }) const {isActive: live} = useActorStatus(profile) if (!moderationOpts) return null const moderation = moderateProfile(profile, moderationOpts) const displayName = sanitizeDisplayName( profile.displayName || sanitizeHandle(profile.handle), moderation.ui('displayName'), ) return ( <> {displayName} {shouldShowVerificationCheckButton(verificationState) && ( )} {sanitizeHandle(profile.handle, '@')} ) } function DevOptions() { const {_} = useLingui() const onboardingDispatch = useOnboardingDispatch() const navigation = useNavigation() const {mutate: deleteChatDeclarationRecord} = useDeleteActorDeclaration() const [actyNotifNudged, setActyNotifNudged] = useActivitySubscriptionsNudged() const resetOnboarding = async () => { navigation.navigate('Home') onboardingDispatch({type: 'start'}) Toast.show(_(msg`Onboarding reset`)) } const clearAllStorage = async () => { await clearStorage() Toast.show(_(msg`Storage cleared, you need to restart the app now.`)) } const onPressUnsnoozeReminder = () => { const lastEmailConfirm = new Date() // wind back 3 days lastEmailConfirm.setDate(lastEmailConfirm.getDate() - 3) persisted.write('reminders', { ...persisted.get('reminders'), lastEmailConfirm: lastEmailConfirm.toISOString(), }) Toast.show(_(msg`You probably want to restart the app now.`)) } const onPressActySubsUnNudge = () => { setActyNotifNudged(false) } return ( <> navigation.navigate('Log')} label={_(msg`Open system log`)}> System log navigation.navigate('Debug')} label={_(msg`Open storybook page`)}> Storybook navigation.navigate('DebugMod')} label={_(msg`Open moderation debug page`)}> Debug Moderation deleteChatDeclarationRecord()} label={_(msg`Open storybook page`)}> Delete chat declaration record resetOnboarding()} label={_(msg`Reset onboarding state`)}> Reset onboarding state Unsnooze email reminder {actyNotifNudged && ( Reset activity subscription nudge )} clearAllStorage()} label={_(msg`Clear all storage data`)}> Clear all storage data (restart after this) ) } function AddAccountRow() { const {_} = useLingui() const {setShowLoggedOut} = useLoggedOutViewControls() const closeEverything = useCloseAllActiveElements() const onAddAnotherAccount = () => { setShowLoggedOut(true) closeEverything() } return ( Add another account ) } function AccountRow({ profile, account, pendingDid, onPressSwitchAccount, }: { profile?: AppBskyActorDefs.ProfileViewDetailed account: SessionAccount pendingDid: string | null onPressSwitchAccount: ( account: SessionAccount, logContext: 'Settings', ) => void }) { const {_} = useLingui() const t = useTheme() const moderationOpts = useModerationOpts() const removePromptControl = Prompt.usePromptControl() const {removeAccount} = useSessionApi() const {isActive: live} = useActorStatus(profile) const onSwitchAccount = () => { if (pendingDid) return onPressSwitchAccount(account, 'Settings') } return ( {moderationOpts && profile ? ( ) : ( )} {sanitizeHandle(account.handle, '@')} {pendingDid === account.did && } {!pendingDid && ( {({props, state}) => ( )} removePromptControl.open()}> Remove account )} { removeAccount(account) Toast.show(_(msg`Account removed from quick access`)) }} confirmButtonCta={_(msg`Remove`)} confirmButtonColor="negative" /> ) }