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,
},
})