import React from 'react' import { ActivityIndicator, Linking, Platform, StyleSheet, Pressable, TextStyle, TouchableOpacity, View, ViewStyle, } from 'react-native' import {useFocusEffect, useNavigation} from '@react-navigation/native' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import * as AppInfo from 'lib/app-info' import {s, colors} from 'lib/styles' import {CenteredView, ScrollView} from '../com/util/Views' import {ViewHeader} from '../com/util/ViewHeader' import {Link, TextLink} from '../com/util/Link' import {Text} from '../com/util/text/Text' import * as Toast from '../com/util/Toast' import {UserAvatar} from '../com/util/UserAvatar' import {ToggleButton} from 'view/com/util/forms/ToggleButton' import {SelectableBtn} from 'view/com/util/forms/SelectableBtn' import {usePalette} from 'lib/hooks/usePalette' import {useCustomPalette} from 'lib/hooks/useCustomPalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher' import {useAnalytics} from 'lib/analytics/analytics' import {NavigationProp} from 'lib/routes/types' import {HandIcon, HashtagIcon} from 'lib/icons' import Clipboard from '@react-native-clipboard/clipboard' import {makeProfileLink} from 'lib/routes/links' import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn' import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile' import {useModalControls} from '#/state/modals' import { useSetMinimalShellMode, useColorMode, useSetColorMode, useOnboardingDispatch, } from '#/state/shell' import { useRequireAltTextEnabled, useSetRequireAltTextEnabled, } from '#/state/preferences' import { useSession, useSessionApi, SessionAccount, getAgent, } from '#/state/session' import {useProfileQuery} from '#/state/queries/profile' import {useClearPreferencesMutation} from '#/state/queries/preferences' import {useInviteCodesQuery} from '#/state/queries/invites' import {clear as clearStorage} from '#/state/persisted/store' import {clearLegacyStorage} from '#/state/persisted/legacy' // TEMPORARY (APP-700) // remove after backend testing finishes // -prf import {useDebugHeaderSetting} from 'lib/api/debug-appview-proxy-header' import {STATUS_PAGE_URL} from 'lib/constants' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' function SettingsAccountCard({account}: {account: SessionAccount}) { const pal = usePalette('default') const {isSwitchingAccounts, currentAccount} = useSession() const {logout} = useSessionApi() const {data: profile} = useProfileQuery({did: account.did}) const isCurrentAccount = account.did === currentAccount?.did const {onPressSwitchAccount} = useAccountSwitcher() const contents = ( {profile?.displayName || account.handle} {account.handle} {isCurrentAccount ? ( Sign out ) : ( )} ) return isCurrentAccount ? ( {contents} ) : ( onPressSwitchAccount(account) } accessibilityRole="button" accessibilityLabel={`Switch to ${account.handle}`} accessibilityHint="Switches the account you are logged in to"> {contents} ) } type Props = NativeStackScreenProps export function SettingsScreen({}: Props) { const queryClient = useQueryClient() const colorMode = useColorMode() const setColorMode = useSetColorMode() const pal = usePalette('default') const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() const requireAltTextEnabled = useRequireAltTextEnabled() const setRequireAltTextEnabled = useSetRequireAltTextEnabled() const onboardingDispatch = useOnboardingDispatch() const navigation = useNavigation() const {isMobile, isTabletOrDesktop} = useWebMediaQueries() const {screen, track} = useAnalytics() const {openModal} = useModalControls() const {isSwitchingAccounts, accounts, currentAccount} = useSession() const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting( getAgent(), ) const {mutate: clearPreferences} = useClearPreferencesMutation() const {data: invites} = useInviteCodesQuery() const invitesAvailable = invites?.available?.length ?? 0 const {setShowLoggedOut} = useLoggedOutViewControls() const closeAllActiveElements = useCloseAllActiveElements() 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 onPressInviteCodes = React.useCallback(() => { track('Settings:InvitecodesButtonClicked') openModal({name: 'invite-codes'}) }, [track, openModal]) const onPressLanguageSettings = React.useCallback(() => { navigation.navigate('LanguageSettings') }, [navigation]) const onPressDeleteAccount = React.useCallback(() => { openModal({name: 'delete-account'}) }, [openModal]) const onPressResetPreferences = React.useCallback(async () => { clearPreferences() }, [clearPreferences]) const onPressResetOnboarding = React.useCallback(async () => { onboardingDispatch({type: 'start'}) Toast.show('Onboarding reset') }, [onboardingDispatch]) const onPressBuildInfo = React.useCallback(() => { Clipboard.setString( `Build version: ${AppInfo.appVersion}; Platform: ${Platform.OS}`, ) Toast.show('Copied build version to clipboard') }, []) const openHomeFeedPreferences = React.useCallback(() => { navigation.navigate('PreferencesHomeFeed') }, [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 onPressSavedFeeds = React.useCallback(() => { navigation.navigate('SavedFeeds') }, [navigation]) const onPressStatusPage = React.useCallback(() => { Linking.openURL(STATUS_PAGE_URL) }, []) const clearAllStorage = React.useCallback(async () => { await clearStorage() Toast.show(`Storage cleared, you need to restart the app now.`) }, []) const clearAllLegacyStorage = React.useCallback(async () => { await clearLegacyStorage() Toast.show(`Legacy storage cleared, you need to restart the app now.`) }, []) return ( {currentAccount ? ( <> Account Email:{' '} {currentAccount.emailConfirmed && ( <> )} {currentAccount.email || '(no email)'}{' '} openModal({name: 'change-email'})}> Change Birthday:{' '} openModal({name: 'birth-date-settings'})}> Show {!currentAccount.emailConfirmed && } ) : null} Signed in as {isSwitchingAccounts ? ( ) : ( )} {accounts .filter(a => a.did !== currentAccount?.did) .map(account => ( ))} Add account Invite a Friend 0 ? primaryBg : pal.btn, ]}> 0 ? primaryText : pal.text) as FontAwesomeIconStyle } /> 0 ? pal.link : pal.text}> {invites?.disabled ? ( Your invite codes are hidden when logged in using an App Password ) : invitesAvailable === 1 ? ( {invitesAvailable} invite code available ) : ( {invitesAvailable} invite codes available )} Accessibility setRequireAltTextEnabled(!requireAltTextEnabled)} /> Appearance setColorMode('system')} accessibilityHint="Set color theme to system setting" /> setColorMode('light')} accessibilityHint="Set color theme to light" /> setColorMode('dark')} accessibilityHint="Set color theme to dark" /> Basics Home Feed Preferences Thread Preferences My Saved Feeds Languages navigation.navigate('Moderation') } accessibilityRole="button" accessibilityHint="" accessibilityLabel={_(msg`Opens moderation settings`)}> Moderation Advanced App passwords Change handle Danger Zone Delete my account… Developer Tools System log {__DEV__ ? ( ) : null} {__DEV__ ? ( <> Storybook Reset preferences state Reset onboarding state Clear all legacy storage data (restart after this) Clear all storage data (restart after this) ) : null} Build version {AppInfo.appVersion} {AppInfo.updateChannel}   ·   Status page ) } 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', alignItems: 'center', paddingLeft: 18, }, desktopContainer: { borderLeftWidth: 1, borderRightWidth: 1, }, noBorder: { borderBottomWidth: 0, borderTopWidth: 0, borderRightWidth: 0, borderLeftWidth: 0, }, })