import React from 'react' import { Platform, Pressable, StyleSheet, TextStyle, TouchableOpacity, View, ViewStyle, } from 'react-native' import {setStringAsync} from 'expo-clipboard' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' import {useAnalytics} from '#/lib/analytics/analytics' import {appVersion, BUNDLE_DATE, bundleInfo} from '#/lib/app-info' import {STATUS_PAGE_URL} from '#/lib/constants' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {useCustomPalette} from '#/lib/hooks/useCustomPalette' import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {HandIcon, HashtagIcon} from '#/lib/icons' import {makeProfileLink} from '#/lib/routes/links' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {NavigationProp} from '#/lib/routes/types' import {colors, s} from '#/lib/styles' import {isNative} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {clearStorage} from '#/state/persisted' import { useInAppBrowser, useSetInAppBrowser, } from '#/state/preferences/in-app-browser' import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useClearPreferencesMutation} from '#/state/queries/preferences' import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile' import {SessionAccount, useSession, useSessionApi} from '#/state/session' import {useOnboardingDispatch, useSetMinimalShellMode} from '#/state/shell' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' import {AccountDropdownBtn} from '#/view/com/util/AccountDropdownBtn' import {ToggleButton} from '#/view/com/util/forms/ToggleButton' import {Link, TextLink} from '#/view/com/util/Link' import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' import {Text} from '#/view/com/util/text/Text' import * as Toast from '#/view/com/util/Toast' import {UserAvatar} from '#/view/com/util/UserAvatar' import {ScrollView} from '#/view/com/util/Views' import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog' import {atoms as a, useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' import {Email2FAToggle} from './Email2FAToggle' import {ExportCarDialog} from './ExportCarDialog' function SettingsAccountCard({ account, pendingDid, onPressSwitchAccount, }: { account: SessionAccount pendingDid: string | null onPressSwitchAccount: ( account: SessionAccount, logContext: 'Settings', ) => void }) { const pal = usePalette('default') const {_} = useLingui() const t = useTheme() const {currentAccount} = useSession() const {data: profile} = useProfileQuery({did: account.did}) const isCurrentAccount = account.did === currentAccount?.did const contents = ( {profile?.displayName || account.handle} {account.handle} ) return isCurrentAccount ? ( {contents} ) : ( onPressSwitchAccount(account, 'Settings') } accessibilityRole="button" accessibilityLabel={_(msg`Switch to ${account.handle}`)} accessibilityHint={_(msg`Switches the account you are logged in to`)} activeOpacity={0.8}> {contents} ) } type Props = NativeStackScreenProps export function SettingsScreen({}: Props) { const queryClient = useQueryClient() const pal = usePalette('default') const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() const onboardingDispatch = useOnboardingDispatch() const navigation = useNavigation() const {isMobile} = useWebMediaQueries() const {screen, track} = useAnalytics() const {openModal} = useModalControls() const {accounts, currentAccount} = useSession() const {mutate: clearPreferences} = useClearPreferencesMutation() const {setShowLoggedOut} = useLoggedOutViewControls() const {logoutEveryAccount} = useSessionApi() const closeAllActiveElements = useCloseAllActiveElements() const exportCarControl = useDialogControl() const birthdayControl = useDialogControl() const {pendingDid, onPressSwitchAccount} = useAccountSwitcher() const isSwitchingAccounts = !!pendingDid // const primaryBg = useCustomPalette({ // light: {backgroundColor: colors.blue0}, // dark: {backgroundColor: colors.blue6}, // }) // const primaryText = useCustomPalette({ // light: {color: colors.blue3}, // dark: {color: colors.blue2}, // }) const dangerBg = useCustomPalette({ light: {backgroundColor: colors.red1}, dark: {backgroundColor: colors.red7}, }) const dangerText = useCustomPalette({ light: {color: colors.red4}, dark: {color: colors.red2}, }) useFocusEffect( React.useCallback(() => { screen('Settings') setMinimalShellMode(false) }, [screen, setMinimalShellMode]), ) const onPressAddAccount = React.useCallback(() => { track('Settings:AddAccountButtonClicked') setShowLoggedOut(true) closeAllActiveElements() }, [track, setShowLoggedOut, closeAllActiveElements]) const onPressChangeHandle = React.useCallback(() => { track('Settings:ChangeHandleButtonClicked') openModal({ name: 'change-handle', onChanged() { if (currentAccount) { // refresh my profile queryClient.invalidateQueries({ queryKey: RQKEY_PROFILE(currentAccount.did), }) } }, }) }, [track, queryClient, openModal, currentAccount]) const onPressExportRepository = React.useCallback(() => { exportCarControl.open() }, [exportCarControl]) const onPressLanguageSettings = React.useCallback(() => { navigation.navigate('LanguageSettings') }, [navigation]) const onPressDeleteAccount = React.useCallback(() => { openModal({name: 'delete-account'}) }, [openModal]) const onPressLogoutEveryAccount = React.useCallback(() => { logoutEveryAccount('Settings') }, [logoutEveryAccount]) const onPressResetPreferences = React.useCallback(async () => { clearPreferences() }, [clearPreferences]) const onPressResetOnboarding = React.useCallback(async () => { navigation.navigate('Home') onboardingDispatch({type: 'start'}) Toast.show(_(msg`Onboarding reset`)) }, [navigation, onboardingDispatch, _]) const onPressBuildInfo = React.useCallback(() => { setStringAsync( `Build version: ${appVersion}; Bundle info: ${bundleInfo}; Bundle date: ${BUNDLE_DATE}; Platform: ${Platform.OS}`, ) Toast.show(_(msg`Copied build version to clipboard`)) }, [_]) const openFollowingFeedPreferences = React.useCallback(() => { navigation.navigate('PreferencesFollowingFeed') }, [navigation]) const openThreadsPreferences = React.useCallback(() => { navigation.navigate('PreferencesThreads') }, [navigation]) const onPressAppPasswords = React.useCallback(() => { navigation.navigate('AppPasswords') }, [navigation]) const onPressSystemLog = React.useCallback(() => { navigation.navigate('Log') }, [navigation]) const onPressStorybook = React.useCallback(() => { navigation.navigate('Debug') }, [navigation]) const onPressDebugModeration = React.useCallback(() => { navigation.navigate('DebugMod') }, [navigation]) const onPressSavedFeeds = React.useCallback(() => { navigation.navigate('SavedFeeds') }, [navigation]) const onPressAccessibilitySettings = React.useCallback(() => { navigation.navigate('AccessibilitySettings') }, [navigation]) const onPressAppearanceSettings = React.useCallback(() => { navigation.navigate('AppearanceSettings') }, [navigation]) const onPressBirthday = React.useCallback(() => { birthdayControl.open() }, [birthdayControl]) const clearAllStorage = React.useCallback(async () => { await clearStorage() Toast.show(_(msg`Storage cleared, you need to restart the app now.`)) }, [_]) const deactivateAccountControl = useDialogControl() const onPressDeactivateAccount = React.useCallback(() => { deactivateAccountControl.open() }, [deactivateAccountControl]) const {mutate: onPressDeleteChatDeclaration} = useDeleteActorDeclaration() return ( Settings {currentAccount ? ( <> Account Email:{' '} {currentAccount.emailConfirmed && ( <> )} {currentAccount.email || '(no email)'} openModal({name: 'change-email'})}> Change Birthday:{' '} Show {!currentAccount.emailConfirmed && } Signed in as ) : null} {accounts.length > 1 && ( Other accounts )} {accounts .filter(a => a.did !== currentAccount?.did) .map(account => ( ))} Add account {accounts.length > 1 ? ( Sign out of all accounts ) : ( Sign out )} Basics Accessibility Appearance Languages navigation.navigate('Moderation') } accessibilityRole="button" accessibilityLabel={_(msg`Moderation settings`)} accessibilityHint={_(msg`Opens moderation settings`)}> Moderation Following Feed Preferences Thread Preferences My Saved Feeds navigation.navigate('MessagesSettings') } accessibilityRole="button" accessibilityLabel={_(msg`Chat settings`)} accessibilityHint={_(msg`Opens chat settings`)}> Chat Settings Privacy navigation.navigate('PreferencesExternalEmbeds') } accessibilityRole="button" accessibilityLabel={_(msg`External media settings`)} accessibilityHint={_(msg`Opens external embeds settings`)}> External Media Preferences Advanced App Passwords Change Handle {isNative && ( setUseInAppBrowser(!inAppBrowserPref)} /> )} Two-factor authentication Account openModal({name: 'change-password'})} accessibilityRole="button" accessibilityLabel={_(msg`Change password`)} accessibilityHint={_( msg`Opens modal for changing your Bluesky password`, )}> Change Password Export My Data Deactivate my account Delete My Account… System log {__DEV__ ? ( <> Storybook Debug Moderation Reset preferences state onPressDeleteChatDeclaration()} accessibilityRole="button" accessibilityLabel={_(msg`Delete chat declaration record`)} accessibilityHint={_(msg`Deletes the chat declaration record`)}> Delete chat declaration record Reset onboarding state Clear all storage data (restart after this) ) : null} Version {appVersion} {bundleInfo} ) } function EmailConfirmationNotice() { const pal = usePalette('default') const palInverted = usePalette('inverted') const {_} = useLingui() const {isMobile} = useWebMediaQueries() const {openModal} = useModalControls() return ( Verify email openModal({name: 'verify-email'})}> Verify My Email Protect your account by verifying your email. ) } const styles = StyleSheet.create({ dimmed: { opacity: 0.5, }, spacer20: { height: 20, }, heading: { paddingHorizontal: 18, paddingBottom: 6, }, infoLine: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 18, paddingBottom: 6, }, profile: { flexDirection: 'row', marginVertical: 6, borderRadius: 4, paddingVertical: 10, paddingHorizontal: 10, }, linkCard: { flexDirection: 'row', alignItems: 'center', paddingVertical: 12, paddingHorizontal: 18, marginBottom: 1, }, linkCardNoIcon: { flexDirection: 'row', alignItems: 'center', paddingVertical: 20, paddingHorizontal: 18, marginBottom: 1, }, toggleCard: { paddingVertical: 8, paddingHorizontal: 6, marginBottom: 1, }, avi: { marginRight: 12, }, iconContainer: { alignItems: 'center', justifyContent: 'center', width: 40, height: 40, borderRadius: 30, marginRight: 12, }, buildInfo: { paddingVertical: 8, }, colorModeText: { marginLeft: 10, marginBottom: 6, }, selectableBtns: { flexDirection: 'row', }, btn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', width: '100%', borderRadius: 32, padding: 14, backgroundColor: colors.gray1, }, toggleBtn: { paddingHorizontal: 0, }, footer: { flex: 1, flexDirection: 'row', paddingLeft: 18, }, })