diff options
-rw-r--r-- | src/components/AccountList.tsx | 15 | ||||
-rw-r--r-- | src/components/dialogs/SwitchAccount.tsx | 4 | ||||
-rw-r--r-- | src/lib/hooks/useAccountSwitcher.ts | 22 | ||||
-rw-r--r-- | src/screens/Login/ChooseAccountForm.tsx | 22 | ||||
-rw-r--r-- | src/view/screens/Settings/index.tsx | 119 |
5 files changed, 104 insertions, 78 deletions
diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index 76596df3e..7d696801e 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -16,12 +16,12 @@ export function AccountList({ onSelectAccount, onSelectOther, otherLabel, - isSwitchingAccounts, + pendingDid, }: { onSelectAccount: (account: SessionAccount) => void onSelectOther: () => void otherLabel?: string - isSwitchingAccounts: boolean + pendingDid: string | null }) { const {currentAccount, accounts} = useSession() const t = useTheme() @@ -33,6 +33,7 @@ export function AccountList({ return ( <View + pointerEvents={pendingDid ? 'none' : 'auto'} style={[ a.rounded_md, a.overflow_hidden, @@ -45,6 +46,7 @@ export function AccountList({ account={account} onSelect={onSelectAccount} isCurrentAccount={account.did === currentAccount?.did} + isPendingAccount={account.did === pendingDid} /> <View style={[a.border_b, t.atoms.border_contrast_low]} /> </React.Fragment> @@ -52,7 +54,7 @@ export function AccountList({ <Button testID="chooseAddAccountBtn" style={[a.flex_1]} - onPress={isSwitchingAccounts ? undefined : onPressAddAccount} + onPress={pendingDid ? undefined : onPressAddAccount} label={_(msg`Login to account that is not listed`)}> {({hovered, pressed}) => ( <View @@ -61,8 +63,7 @@ export function AccountList({ a.flex_row, a.align_center, {height: 48}, - (hovered || pressed || isSwitchingAccounts) && - t.atoms.bg_contrast_25, + (hovered || pressed) && t.atoms.bg_contrast_25, ]}> <Text style={[ @@ -86,10 +87,12 @@ function AccountItem({ account, onSelect, isCurrentAccount, + isPendingAccount, }: { account: SessionAccount onSelect: (account: SessionAccount) => void isCurrentAccount: boolean + isPendingAccount: boolean }) { const t = useTheme() const {_} = useLingui() @@ -117,7 +120,7 @@ function AccountItem({ a.flex_row, a.align_center, {height: 48}, - (hovered || pressed) && t.atoms.bg_contrast_25, + (hovered || pressed || isPendingAccount) && t.atoms.bg_contrast_25, ]}> <View style={a.p_md}> <UserAvatar avatar={profile?.avatar} size={24} /> diff --git a/src/components/dialogs/SwitchAccount.tsx b/src/components/dialogs/SwitchAccount.tsx index 18fc55aaf..0bd4bcb8c 100644 --- a/src/components/dialogs/SwitchAccount.tsx +++ b/src/components/dialogs/SwitchAccount.tsx @@ -18,7 +18,7 @@ export function SwitchAccountDialog({ }) { const {_} = useLingui() const {currentAccount} = useSession() - const {onPressSwitchAccount, isSwitchingAccounts} = useAccountSwitcher() + const {onPressSwitchAccount, pendingDid} = useAccountSwitcher() const {setShowLoggedOut} = useLoggedOutViewControls() const onSelectAccount = useCallback( @@ -54,7 +54,7 @@ export function SwitchAccountDialog({ onSelectAccount={onSelectAccount} onSelectOther={onPressAddAccount} otherLabel={_(msg`Add account`)} - isSwitchingAccounts={isSwitchingAccounts} + pendingDid={pendingDid} /> </View> </Dialog.ScrollableInner> diff --git a/src/lib/hooks/useAccountSwitcher.ts b/src/lib/hooks/useAccountSwitcher.ts index d4e026958..de50e5336 100644 --- a/src/lib/hooks/useAccountSwitcher.ts +++ b/src/lib/hooks/useAccountSwitcher.ts @@ -12,7 +12,7 @@ import {logEvent} from '../statsig/statsig' import {LogEvents} from '../statsig/statsig' export function useAccountSwitcher() { - const [isSwitchingAccounts, setIsSwitchingAccounts] = useState(false) + const [pendingDid, setPendingDid] = useState<string | null>(null) const {_} = useLingui() const {track} = useAnalytics() const {initSession, clearCurrentAccount} = useSessionApi() @@ -24,9 +24,12 @@ export function useAccountSwitcher() { logContext: LogEvents['account:loggedIn']['logContext'], ) => { track('Settings:SwitchAccountButtonClicked') - + if (pendingDid) { + // The session API isn't resilient to race conditions so let's just ignore this. + return + } try { - setIsSwitchingAccounts(true) + setPendingDid(account.did) if (account.accessJwt) { if (isWeb) { // We're switching accounts, which remounts the entire app. @@ -57,11 +60,18 @@ export function useAccountSwitcher() { Toast.show(_(msg`Sorry! We need you to enter your password.`)) }, 100) } finally { - setIsSwitchingAccounts(false) + setPendingDid(null) } }, - [_, track, clearCurrentAccount, initSession, requestSwitchToAccount], + [ + _, + track, + clearCurrentAccount, + initSession, + requestSwitchToAccount, + pendingDid, + ], ) - return {onPressSwitchAccount, isSwitchingAccounts} + return {onPressSwitchAccount, pendingDid} } diff --git a/src/screens/Login/ChooseAccountForm.tsx b/src/screens/Login/ChooseAccountForm.tsx index 8a58ac03d..e097e0db8 100644 --- a/src/screens/Login/ChooseAccountForm.tsx +++ b/src/screens/Login/ChooseAccountForm.tsx @@ -22,7 +22,7 @@ export const ChooseAccountForm = ({ onSelectAccount: (account?: SessionAccount) => void onPressBack: () => void }) => { - const [isSwitchingAccounts, setIsSwitchingAccounts] = React.useState(false) + const [pendingDid, setPendingDid] = React.useState<string | null>(null) const {track, screen} = useAnalytics() const {_} = useLingui() const {currentAccount} = useSession() @@ -35,13 +35,17 @@ export const ChooseAccountForm = ({ const onSelect = React.useCallback( async (account: SessionAccount) => { + if (pendingDid) { + // The session API isn't resilient to race conditions so let's just ignore this. + return + } if (account.accessJwt) { if (account.did === currentAccount?.did) { setShowLoggedOut(false) Toast.show(_(msg`Already signed in as @${account.handle}`)) } else { try { - setIsSwitchingAccounts(true) + setPendingDid(account.did) await initSession(account) logEvent('account:loggedIn', { logContext: 'ChooseAccountForm', @@ -57,14 +61,22 @@ export const ChooseAccountForm = ({ }) onSelectAccount(account) } finally { - setIsSwitchingAccounts(false) + setPendingDid(null) } } } else { onSelectAccount(account) } }, - [currentAccount, track, initSession, onSelectAccount, setShowLoggedOut, _], + [ + currentAccount, + track, + initSession, + pendingDid, + onSelectAccount, + setShowLoggedOut, + _, + ], ) return ( @@ -78,7 +90,7 @@ export const ChooseAccountForm = ({ <AccountList onSelectAccount={onSelect} onSelectOther={() => onSelectAccount()} - isSwitchingAccounts={isSwitchingAccounts} + pendingDid={pendingDid} /> </View> <View style={[a.flex_row]}> diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index b3313f60f..2e4e8c10a 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -1,6 +1,5 @@ import React from 'react' import { - ActivityIndicator, Linking, Platform, Pressable, @@ -63,6 +62,7 @@ import * as Toast from 'view/com/util/Toast' import {UserAvatar} from 'view/com/util/UserAvatar' import {ScrollView} from 'view/com/util/Views' import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' +import {useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' import * as TextField from '#/components/forms/TextField' @@ -72,11 +72,11 @@ import {ExportCarDialog} from './ExportCarDialog' function SettingsAccountCard({ account, - isSwitchingAccounts, + pendingDid, onPressSwitchAccount, }: { account: SessionAccount - isSwitchingAccounts: boolean + pendingDid: string | null onPressSwitchAccount: ( account: SessionAccount, logContext: 'Settings', @@ -84,13 +84,19 @@ function SettingsAccountCard({ }) { const pal = usePalette('default') const {_} = useLingui() + const t = useTheme() const {currentAccount} = useSession() const {logout} = useSessionApi() const {data: profile} = useProfileQuery({did: account.did}) const isCurrentAccount = account.did === currentAccount?.did const contents = ( - <View style={[pal.view, styles.linkCard]}> + <View + style={[ + pal.view, + styles.linkCard, + account.did === pendingDid && t.atoms.bg_contrast_25, + ]}> <View style={styles.avi}> <UserAvatar size={40} @@ -122,7 +128,8 @@ function SettingsAccountCard({ }} accessibilityRole="button" accessibilityLabel={_(msg`Sign out`)} - accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}> + accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`} + activeOpacity={0.8}> <Text type="lg" style={pal.link}> <Trans>Sign out</Trans> </Text> @@ -148,13 +155,12 @@ function SettingsAccountCard({ testID={`switchToAccountBtn-${account.handle}`} key={account.did} onPress={ - isSwitchingAccounts - ? undefined - : () => onPressSwitchAccount(account, 'Settings') + pendingDid ? undefined : () => onPressSwitchAccount(account, 'Settings') } accessibilityRole="button" accessibilityLabel={_(msg`Switch to ${account.handle}`)} - accessibilityHint={_(msg`Switches the account you are logged in to`)}> + accessibilityHint={_(msg`Switches the account you are logged in to`)} + activeOpacity={0.8}> {contents} </TouchableOpacity> ) @@ -181,7 +187,8 @@ export function SettingsScreen({}: Props) { const closeAllActiveElements = useCloseAllActiveElements() const exportCarControl = useDialogControl() const birthdayControl = useDialogControl() - const {isSwitchingAccounts, onPressSwitchAccount} = useAccountSwitcher() + const {pendingDid, onPressSwitchAccount} = useAccountSwitcher() + const isSwitchingAccounts = !!pendingDid // TODO: TEMP REMOVE WHEN CLOPS ARE RELEASED const gate = useGate() @@ -382,59 +389,53 @@ export function SettingsScreen({}: Props) { <View style={styles.spacer20} /> {!currentAccount.emailConfirmed && <EmailConfirmationNotice />} + + <View style={[s.flexRow, styles.heading]}> + <Text type="xl-bold" style={pal.text}> + <Trans>Signed in as</Trans> + </Text> + <View style={s.flex1} /> + </View> + <View pointerEvents={pendingDid ? 'none' : 'auto'}> + <SettingsAccountCard + account={currentAccount} + onPressSwitchAccount={onPressSwitchAccount} + pendingDid={pendingDid} + /> + </View> </> ) : null} - <View style={[s.flexRow, styles.heading]}> - <Text type="xl-bold" style={pal.text}> - <Trans>Signed in as</Trans> - </Text> - <View style={s.flex1} /> - </View> - - {isSwitchingAccounts ? ( - <View style={[pal.view, styles.linkCard]}> - <ActivityIndicator /> - </View> - ) : ( - <SettingsAccountCard - account={currentAccount!} - onPressSwitchAccount={onPressSwitchAccount} - isSwitchingAccounts={isSwitchingAccounts} - /> - )} - {accounts - .filter(a => a.did !== currentAccount?.did) - .map(account => ( - <SettingsAccountCard - key={account.did} - account={account} - onPressSwitchAccount={onPressSwitchAccount} - isSwitchingAccounts={isSwitchingAccounts} - /> - ))} + <View pointerEvents={pendingDid ? 'none' : 'auto'}> + {accounts + .filter(a => a.did !== currentAccount?.did) + .map(account => ( + <SettingsAccountCard + key={account.did} + account={account} + onPressSwitchAccount={onPressSwitchAccount} + pendingDid={pendingDid} + /> + ))} - <TouchableOpacity - testID="switchToNewAccountBtn" - style={[ - styles.linkCard, - pal.view, - isSwitchingAccounts && styles.dimmed, - ]} - onPress={isSwitchingAccounts ? undefined : onPressAddAccount} - accessibilityRole="button" - accessibilityLabel={_(msg`Add account`)} - accessibilityHint={_(msg`Create a new Bluesky account`)}> - <View style={[styles.iconContainer, pal.btn]}> - <FontAwesomeIcon - icon="plus" - style={pal.text as FontAwesomeIconStyle} - /> - </View> - <Text type="lg" style={pal.text}> - <Trans>Add account</Trans> - </Text> - </TouchableOpacity> + <TouchableOpacity + testID="switchToNewAccountBtn" + style={[styles.linkCard, pal.view]} + onPress={isSwitchingAccounts ? undefined : onPressAddAccount} + accessibilityRole="button" + accessibilityLabel={_(msg`Add account`)} + accessibilityHint={_(msg`Create a new Bluesky account`)}> + <View style={[styles.iconContainer, pal.btn]}> + <FontAwesomeIcon + icon="plus" + style={pal.text as FontAwesomeIconStyle} + /> + </View> + <Text type="lg" style={pal.text}> + <Trans>Add account</Trans> + </Text> + </TouchableOpacity> + </View> <View style={styles.spacer20} /> |