mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve account switcher pending state (#3827)

* Protect against races

* Reduce UI jank when switching accounts

* Add pending state to selected account

* Disable presses while pending

authored by danabra.mov and committed by

GitHub b86c3b48 8ba1b10c

+104 -78
+9 -6
src/components/AccountList.tsx
··· 16 16 onSelectAccount, 17 17 onSelectOther, 18 18 otherLabel, 19 - isSwitchingAccounts, 19 + pendingDid, 20 20 }: { 21 21 onSelectAccount: (account: SessionAccount) => void 22 22 onSelectOther: () => void 23 23 otherLabel?: string 24 - isSwitchingAccounts: boolean 24 + pendingDid: string | null 25 25 }) { 26 26 const {currentAccount, accounts} = useSession() 27 27 const t = useTheme() ··· 33 33 34 34 return ( 35 35 <View 36 + pointerEvents={pendingDid ? 'none' : 'auto'} 36 37 style={[ 37 38 a.rounded_md, 38 39 a.overflow_hidden, ··· 45 46 account={account} 46 47 onSelect={onSelectAccount} 47 48 isCurrentAccount={account.did === currentAccount?.did} 49 + isPendingAccount={account.did === pendingDid} 48 50 /> 49 51 <View style={[a.border_b, t.atoms.border_contrast_low]} /> 50 52 </React.Fragment> ··· 52 54 <Button 53 55 testID="chooseAddAccountBtn" 54 56 style={[a.flex_1]} 55 - onPress={isSwitchingAccounts ? undefined : onPressAddAccount} 57 + onPress={pendingDid ? undefined : onPressAddAccount} 56 58 label={_(msg`Login to account that is not listed`)}> 57 59 {({hovered, pressed}) => ( 58 60 <View ··· 61 63 a.flex_row, 62 64 a.align_center, 63 65 {height: 48}, 64 - (hovered || pressed || isSwitchingAccounts) && 65 - t.atoms.bg_contrast_25, 66 + (hovered || pressed) && t.atoms.bg_contrast_25, 66 67 ]}> 67 68 <Text 68 69 style={[ ··· 86 87 account, 87 88 onSelect, 88 89 isCurrentAccount, 90 + isPendingAccount, 89 91 }: { 90 92 account: SessionAccount 91 93 onSelect: (account: SessionAccount) => void 92 94 isCurrentAccount: boolean 95 + isPendingAccount: boolean 93 96 }) { 94 97 const t = useTheme() 95 98 const {_} = useLingui() ··· 117 120 a.flex_row, 118 121 a.align_center, 119 122 {height: 48}, 120 - (hovered || pressed) && t.atoms.bg_contrast_25, 123 + (hovered || pressed || isPendingAccount) && t.atoms.bg_contrast_25, 121 124 ]}> 122 125 <View style={a.p_md}> 123 126 <UserAvatar avatar={profile?.avatar} size={24} />
+2 -2
src/components/dialogs/SwitchAccount.tsx
··· 18 18 }) { 19 19 const {_} = useLingui() 20 20 const {currentAccount} = useSession() 21 - const {onPressSwitchAccount, isSwitchingAccounts} = useAccountSwitcher() 21 + const {onPressSwitchAccount, pendingDid} = useAccountSwitcher() 22 22 const {setShowLoggedOut} = useLoggedOutViewControls() 23 23 24 24 const onSelectAccount = useCallback( ··· 54 54 onSelectAccount={onSelectAccount} 55 55 onSelectOther={onPressAddAccount} 56 56 otherLabel={_(msg`Add account`)} 57 - isSwitchingAccounts={isSwitchingAccounts} 57 + pendingDid={pendingDid} 58 58 /> 59 59 </View> 60 60 </Dialog.ScrollableInner>
+16 -6
src/lib/hooks/useAccountSwitcher.ts
··· 12 12 import {LogEvents} from '../statsig/statsig' 13 13 14 14 export function useAccountSwitcher() { 15 - const [isSwitchingAccounts, setIsSwitchingAccounts] = useState(false) 15 + const [pendingDid, setPendingDid] = useState<string | null>(null) 16 16 const {_} = useLingui() 17 17 const {track} = useAnalytics() 18 18 const {initSession, clearCurrentAccount} = useSessionApi() ··· 24 24 logContext: LogEvents['account:loggedIn']['logContext'], 25 25 ) => { 26 26 track('Settings:SwitchAccountButtonClicked') 27 - 27 + if (pendingDid) { 28 + // The session API isn't resilient to race conditions so let's just ignore this. 29 + return 30 + } 28 31 try { 29 - setIsSwitchingAccounts(true) 32 + setPendingDid(account.did) 30 33 if (account.accessJwt) { 31 34 if (isWeb) { 32 35 // We're switching accounts, which remounts the entire app. ··· 57 60 Toast.show(_(msg`Sorry! We need you to enter your password.`)) 58 61 }, 100) 59 62 } finally { 60 - setIsSwitchingAccounts(false) 63 + setPendingDid(null) 61 64 } 62 65 }, 63 - [_, track, clearCurrentAccount, initSession, requestSwitchToAccount], 66 + [ 67 + _, 68 + track, 69 + clearCurrentAccount, 70 + initSession, 71 + requestSwitchToAccount, 72 + pendingDid, 73 + ], 64 74 ) 65 75 66 - return {onPressSwitchAccount, isSwitchingAccounts} 76 + return {onPressSwitchAccount, pendingDid} 67 77 }
+17 -5
src/screens/Login/ChooseAccountForm.tsx
··· 22 22 onSelectAccount: (account?: SessionAccount) => void 23 23 onPressBack: () => void 24 24 }) => { 25 - const [isSwitchingAccounts, setIsSwitchingAccounts] = React.useState(false) 25 + const [pendingDid, setPendingDid] = React.useState<string | null>(null) 26 26 const {track, screen} = useAnalytics() 27 27 const {_} = useLingui() 28 28 const {currentAccount} = useSession() ··· 35 35 36 36 const onSelect = React.useCallback( 37 37 async (account: SessionAccount) => { 38 + if (pendingDid) { 39 + // The session API isn't resilient to race conditions so let's just ignore this. 40 + return 41 + } 38 42 if (account.accessJwt) { 39 43 if (account.did === currentAccount?.did) { 40 44 setShowLoggedOut(false) 41 45 Toast.show(_(msg`Already signed in as @${account.handle}`)) 42 46 } else { 43 47 try { 44 - setIsSwitchingAccounts(true) 48 + setPendingDid(account.did) 45 49 await initSession(account) 46 50 logEvent('account:loggedIn', { 47 51 logContext: 'ChooseAccountForm', ··· 57 61 }) 58 62 onSelectAccount(account) 59 63 } finally { 60 - setIsSwitchingAccounts(false) 64 + setPendingDid(null) 61 65 } 62 66 } 63 67 } else { 64 68 onSelectAccount(account) 65 69 } 66 70 }, 67 - [currentAccount, track, initSession, onSelectAccount, setShowLoggedOut, _], 71 + [ 72 + currentAccount, 73 + track, 74 + initSession, 75 + pendingDid, 76 + onSelectAccount, 77 + setShowLoggedOut, 78 + _, 79 + ], 68 80 ) 69 81 70 82 return ( ··· 78 90 <AccountList 79 91 onSelectAccount={onSelect} 80 92 onSelectOther={() => onSelectAccount()} 81 - isSwitchingAccounts={isSwitchingAccounts} 93 + pendingDid={pendingDid} 82 94 /> 83 95 </View> 84 96 <View style={[a.flex_row]}>
+60 -59
src/view/screens/Settings/index.tsx
··· 1 1 import React from 'react' 2 2 import { 3 - ActivityIndicator, 4 3 Linking, 5 4 Platform, 6 5 Pressable, ··· 63 62 import {UserAvatar} from 'view/com/util/UserAvatar' 64 63 import {ScrollView} from 'view/com/util/Views' 65 64 import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' 65 + import {useTheme} from '#/alf' 66 66 import {useDialogControl} from '#/components/Dialog' 67 67 import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' 68 68 import * as TextField from '#/components/forms/TextField' ··· 72 72 73 73 function SettingsAccountCard({ 74 74 account, 75 - isSwitchingAccounts, 75 + pendingDid, 76 76 onPressSwitchAccount, 77 77 }: { 78 78 account: SessionAccount 79 - isSwitchingAccounts: boolean 79 + pendingDid: string | null 80 80 onPressSwitchAccount: ( 81 81 account: SessionAccount, 82 82 logContext: 'Settings', ··· 84 84 }) { 85 85 const pal = usePalette('default') 86 86 const {_} = useLingui() 87 + const t = useTheme() 87 88 const {currentAccount} = useSession() 88 89 const {logout} = useSessionApi() 89 90 const {data: profile} = useProfileQuery({did: account.did}) 90 91 const isCurrentAccount = account.did === currentAccount?.did 91 92 92 93 const contents = ( 93 - <View style={[pal.view, styles.linkCard]}> 94 + <View 95 + style={[ 96 + pal.view, 97 + styles.linkCard, 98 + account.did === pendingDid && t.atoms.bg_contrast_25, 99 + ]}> 94 100 <View style={styles.avi}> 95 101 <UserAvatar 96 102 size={40} ··· 122 128 }} 123 129 accessibilityRole="button" 124 130 accessibilityLabel={_(msg`Sign out`)} 125 - accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}> 131 + accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`} 132 + activeOpacity={0.8}> 126 133 <Text type="lg" style={pal.link}> 127 134 <Trans>Sign out</Trans> 128 135 </Text> ··· 148 155 testID={`switchToAccountBtn-${account.handle}`} 149 156 key={account.did} 150 157 onPress={ 151 - isSwitchingAccounts 152 - ? undefined 153 - : () => onPressSwitchAccount(account, 'Settings') 158 + pendingDid ? undefined : () => onPressSwitchAccount(account, 'Settings') 154 159 } 155 160 accessibilityRole="button" 156 161 accessibilityLabel={_(msg`Switch to ${account.handle}`)} 157 - accessibilityHint={_(msg`Switches the account you are logged in to`)}> 162 + accessibilityHint={_(msg`Switches the account you are logged in to`)} 163 + activeOpacity={0.8}> 158 164 {contents} 159 165 </TouchableOpacity> 160 166 ) ··· 181 187 const closeAllActiveElements = useCloseAllActiveElements() 182 188 const exportCarControl = useDialogControl() 183 189 const birthdayControl = useDialogControl() 184 - const {isSwitchingAccounts, onPressSwitchAccount} = useAccountSwitcher() 190 + const {pendingDid, onPressSwitchAccount} = useAccountSwitcher() 191 + const isSwitchingAccounts = !!pendingDid 185 192 186 193 // TODO: TEMP REMOVE WHEN CLOPS ARE RELEASED 187 194 const gate = useGate() ··· 382 389 <View style={styles.spacer20} /> 383 390 384 391 {!currentAccount.emailConfirmed && <EmailConfirmationNotice />} 392 + 393 + <View style={[s.flexRow, styles.heading]}> 394 + <Text type="xl-bold" style={pal.text}> 395 + <Trans>Signed in as</Trans> 396 + </Text> 397 + <View style={s.flex1} /> 398 + </View> 399 + <View pointerEvents={pendingDid ? 'none' : 'auto'}> 400 + <SettingsAccountCard 401 + account={currentAccount} 402 + onPressSwitchAccount={onPressSwitchAccount} 403 + pendingDid={pendingDid} 404 + /> 405 + </View> 385 406 </> 386 407 ) : null} 387 - <View style={[s.flexRow, styles.heading]}> 388 - <Text type="xl-bold" style={pal.text}> 389 - <Trans>Signed in as</Trans> 390 - </Text> 391 - <View style={s.flex1} /> 392 - </View> 393 408 394 - {isSwitchingAccounts ? ( 395 - <View style={[pal.view, styles.linkCard]}> 396 - <ActivityIndicator /> 397 - </View> 398 - ) : ( 399 - <SettingsAccountCard 400 - account={currentAccount!} 401 - onPressSwitchAccount={onPressSwitchAccount} 402 - isSwitchingAccounts={isSwitchingAccounts} 403 - /> 404 - )} 409 + <View pointerEvents={pendingDid ? 'none' : 'auto'}> 410 + {accounts 411 + .filter(a => a.did !== currentAccount?.did) 412 + .map(account => ( 413 + <SettingsAccountCard 414 + key={account.did} 415 + account={account} 416 + onPressSwitchAccount={onPressSwitchAccount} 417 + pendingDid={pendingDid} 418 + /> 419 + ))} 405 420 406 - {accounts 407 - .filter(a => a.did !== currentAccount?.did) 408 - .map(account => ( 409 - <SettingsAccountCard 410 - key={account.did} 411 - account={account} 412 - onPressSwitchAccount={onPressSwitchAccount} 413 - isSwitchingAccounts={isSwitchingAccounts} 414 - /> 415 - ))} 416 - 417 - <TouchableOpacity 418 - testID="switchToNewAccountBtn" 419 - style={[ 420 - styles.linkCard, 421 - pal.view, 422 - isSwitchingAccounts && styles.dimmed, 423 - ]} 424 - onPress={isSwitchingAccounts ? undefined : onPressAddAccount} 425 - accessibilityRole="button" 426 - accessibilityLabel={_(msg`Add account`)} 427 - accessibilityHint={_(msg`Create a new Bluesky account`)}> 428 - <View style={[styles.iconContainer, pal.btn]}> 429 - <FontAwesomeIcon 430 - icon="plus" 431 - style={pal.text as FontAwesomeIconStyle} 432 - /> 433 - </View> 434 - <Text type="lg" style={pal.text}> 435 - <Trans>Add account</Trans> 436 - </Text> 437 - </TouchableOpacity> 421 + <TouchableOpacity 422 + testID="switchToNewAccountBtn" 423 + style={[styles.linkCard, pal.view]} 424 + onPress={isSwitchingAccounts ? undefined : onPressAddAccount} 425 + accessibilityRole="button" 426 + accessibilityLabel={_(msg`Add account`)} 427 + accessibilityHint={_(msg`Create a new Bluesky account`)}> 428 + <View style={[styles.iconContainer, pal.btn]}> 429 + <FontAwesomeIcon 430 + icon="plus" 431 + style={pal.text as FontAwesomeIconStyle} 432 + /> 433 + </View> 434 + <Text type="lg" style={pal.text}> 435 + <Trans>Add account</Trans> 436 + </Text> 437 + </TouchableOpacity> 438 + </View> 438 439 439 440 <View style={styles.spacer20} /> 440 441