import React from 'react' import { ActivityIndicator, Linking, Platform, Pressable, StyleSheet, TextStyle, TouchableOpacity, View, ViewStyle, } from 'react-native' import { useFocusEffect, useNavigation, StackActions, } from '@react-navigation/native' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {observer} from 'mobx-react-lite' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {withAuthRequired} from 'view/com/auth/withAuthRequired' import * as AppInfo from 'lib/app-info' import {useStores} from 'state/index' import {s, colors} from 'lib/styles' import {ScrollView} from '../com/util/Views' import {ViewHeader} from '../com/util/ViewHeader' import {Link} 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 {AccountData} from 'state/models/session' import {useAnalytics} from 'lib/analytics/analytics' import {NavigationProp} from 'lib/routes/types' import {isDesktopWeb} from 'platform/detection' import {pluralize} from 'lib/strings/helpers' import {formatCount} from 'view/com/util/numeric/format' import Clipboard from '@react-native-clipboard/clipboard' import {reset as resetNavigation} from '../../Navigation' import {makeProfileLink} from 'lib/routes/links' // 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 {DropdownItem, NativeDropdown} from 'view/com/util/forms/NativeDropdown' type Props = NativeStackScreenProps export const SettingsScreen = withAuthRequired( observer(function Settings({}: Props) { const pal = usePalette('default') const store = useStores() const navigation = useNavigation() const {screen, track} = useAnalytics() const [isSwitching, setIsSwitching] = React.useState(false) const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting( store.agent, ) 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') store.shell.setMinimalShellMode(false) }, [screen, store]), ) const onPressSwitchAccount = React.useCallback( async (acct: AccountData) => { track('Settings:SwitchAccountButtonClicked') setIsSwitching(true) if (await store.session.resumeSession(acct)) { setIsSwitching(false) resetNavigation() Toast.show(`Signed in as ${acct.displayName || acct.handle}`) return } setIsSwitching(false) Toast.show('Sorry! We need you to enter your password.') navigation.navigate('HomeTab') navigation.dispatch(StackActions.popToTop()) store.session.clear() }, [track, setIsSwitching, navigation, store], ) const onPressAddAccount = React.useCallback(() => { track('Settings:AddAccountButtonClicked') navigation.navigate('HomeTab') navigation.dispatch(StackActions.popToTop()) store.session.clear() }, [track, navigation, store]) const onPressChangeHandle = React.useCallback(() => { track('Settings:ChangeHandleButtonClicked') store.shell.openModal({ name: 'change-handle', onChanged() { setIsSwitching(true) store.session.reloadFromServer().then( () => { setIsSwitching(false) Toast.show('Your handle has been updated') }, err => { store.log.error( 'Failed to reload from server after handle update', {err}, ) setIsSwitching(false) }, ) }, }) }, [track, store, setIsSwitching]) const onPressInviteCodes = React.useCallback(() => { track('Settings:InvitecodesButtonClicked') store.shell.openModal({name: 'invite-codes'}) }, [track, store]) const onPressContentLanguages = React.useCallback(() => { track('Settings:ContentlanguagesButtonClicked') store.shell.openModal({name: 'content-languages-settings'}) }, [track, store]) const onPressSignout = React.useCallback(() => { track('Settings:SignOutButtonClicked') store.session.logout() }, [track, store]) const onPressDeleteAccount = React.useCallback(() => { store.shell.openModal({name: 'delete-account'}) }, [store]) const onPressResetPreferences = React.useCallback(async () => { await store.preferences.reset() Toast.show('Preferences reset') }, [store]) const onPressBuildInfo = React.useCallback(() => { Clipboard.setString( `Build version: ${AppInfo.appVersion}; Platform: ${Platform.OS}`, ) Toast.show('Copied build version to clipboard') }, []) const openPreferencesModal = React.useCallback(() => { store.shell.openModal({ name: 'preferences-home-feed', }) }, [store]) 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) }, []) return ( {store.session.currentSession !== undefined ? ( <> Account Email:{' '} {store.session.currentSession?.email} ) : null} Signed in as {isSwitching ? ( ) : ( {store.me.displayName || store.me.handle} {store.me.handle} Sign out )} {store.session.switchableAccounts.map(account => ( onPressSwitchAccount(account) } accessibilityRole="button" accessibilityLabel={`Switch to ${account.handle}`} accessibilityHint="Switches the account you are logged in to"> {account.displayName || account.handle} {account.handle} ))} Add account Invite a Friend 0 ? primaryBg : pal.btn, ]}> 0 ? primaryText : pal.text) as FontAwesomeIconStyle } /> 0 ? pal.link : pal.text}> {formatCount(store.me.invitesAvailable)} invite{' '} {pluralize(store.me.invitesAvailable, 'code')} available Accessibility Appearance store.shell.setColorMode('system')} accessibilityHint="Set color theme to system setting" /> store.shell.setColorMode('light')} accessibilityHint="Set color theme to light" /> store.shell.setColorMode('dark')} accessibilityHint="Set color theme to dark" /> Advanced Home Feed Preferences App passwords Saved Feeds Content languages Change handle Danger Zone Delete my account… Developer Tools System log {isDesktopWeb ? ( ) : null} {__DEV__ ? ( <> Storybook Reset preferences state ) : null} Build version {AppInfo.appVersion} {AppInfo.updateChannel} ·   Status page ) }), ) function AccountDropdownBtn({handle}: {handle: string}) { const store = useStores() const pal = usePalette('default') const items: DropdownItem[] = [ { label: 'Remove account', onPress: () => { store.session.removeAccount(handle) Toast.show('Account removed from quick access') }, icon: { ios: { name: 'trash', }, android: 'ic_delete', web: 'trash', }, }, ] return ( ) } const styles = StyleSheet.create({ dimmed: { opacity: 0.5, }, spacer20: { height: 20, }, heading: { paddingHorizontal: 18, paddingBottom: 6, }, infoLine: { 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, }, })