edit mode on switch account dialog

This commit is contained in:
Samuel Newman
2024-10-21 13:03:07 +03:00
parent 72270fd505
commit cc5d218cef
5 changed files with 169 additions and 67 deletions
+126 -57
View File
@@ -7,30 +7,34 @@ import {useLingui} from '@lingui/react'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {useProfilesQuery} from '#/state/queries/profile'
import {type SessionAccount, useSession} from '#/state/session'
import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {ChevronRight_Stroke2_Corner0_Rounded as Chevron} from '#/components/icons/Chevron'
import {Button} from './Button'
import {Text} from './Typography'
import {Button, ButtonText} from '#/components/Button'
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronIcon} from '#/components/icons/Chevron'
import {Text} from '#/components/Typography'
import {useDialogContext} from './Dialog'
export function AccountList({
onSelectAccount,
onSelectOther,
otherLabel,
pendingDid,
editing = false,
}: {
onSelectAccount: (account: SessionAccount) => void
onSelectOther: () => void
otherLabel?: string
pendingDid: string | null
editing?: boolean
}) {
const {currentAccount, accounts} = useSession()
const t = useTheme()
const {_} = useLingui()
const {data: profiles} = useProfilesQuery({
handles: accounts.map(acc => acc.did),
keepPrevious: true,
})
const onPressAddAccount = useCallback(() => {
@@ -46,46 +50,55 @@ export function AccountList({
{borderWidth: 1},
t.atoms.border_contrast_low,
]}>
{accounts.map(account => (
<React.Fragment key={account.did}>
{accounts.map((account, i) => (
<View
key={account.did}
style={
i !== accounts.length - 1 && [
{borderBottomWidth: 1},
t.atoms.border_contrast_low,
]
}>
<AccountItem
profile={profiles?.profiles.find(p => p.did === account.did)}
account={account}
onSelect={onSelectAccount}
isCurrentAccount={account.did === currentAccount?.did}
isPendingAccount={account.did === pendingDid}
editing={editing}
/>
<View style={[{borderBottomWidth: 1}, t.atoms.border_contrast_low]} />
</React.Fragment>
</View>
))}
<Button
testID="chooseAddAccountBtn"
style={[a.flex_1]}
onPress={pendingDid ? undefined : onPressAddAccount}
label={_(msg`Login to account that is not listed`)}>
{({hovered, pressed}) => (
<View
style={[
a.flex_1,
a.flex_row,
a.align_center,
{height: 48},
(hovered || pressed) && t.atoms.bg_contrast_25,
]}>
<Text
{!editing && (
<Button
testID="chooseAddAccountBtn"
style={[a.flex_1, {borderTopWidth: 1}, t.atoms.border_contrast_low]}
onPress={pendingDid ? undefined : onPressAddAccount}
label={_(msg`Login to account that is not listed`)}>
{({hovered, pressed}) => (
<View
style={[
a.align_baseline,
a.flex_1,
a.flex_row,
a.py_sm,
{paddingLeft: 48},
a.align_center,
{height: 48},
(hovered || pressed) && t.atoms.bg_contrast_25,
]}>
{otherLabel ?? <Trans>Other account</Trans>}
</Text>
<Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
</View>
)}
</Button>
<Text
style={[
a.align_baseline,
a.flex_1,
a.flex_row,
a.py_sm,
{paddingLeft: 48},
]}>
{otherLabel ?? <Trans>Other account</Trans>}
</Text>
<ChevronIcon size="sm" style={[t.atoms.text, a.mr_md]} />
</View>
)}
</Button>
)}
</View>
)
}
@@ -96,20 +109,52 @@ function AccountItem({
onSelect,
isCurrentAccount,
isPendingAccount,
editing,
}: {
profile?: AppBskyActorDefs.ProfileViewDetailed
account: SessionAccount
onSelect: (account: SessionAccount) => void
isCurrentAccount: boolean
isPendingAccount: boolean
editing?: boolean
}) {
const t = useTheme()
const {_} = useLingui()
const control = useDialogContext()
const {logoutCurrentAccount, removeAccount} = useSessionApi()
const onPress = useCallback(() => {
onSelect(account)
}, [account, onSelect])
const onPressRemove = useCallback(() => {
if (isCurrentAccount) {
control.close(() => {
logoutCurrentAccount('AccountList')
})
} else {
removeAccount(account)
}
}, [control, logoutCurrentAccount, removeAccount, account, isCurrentAccount])
if (editing) {
return (
<AccountItemContent profile={profile} account={account}>
<Button
label={isCurrentAccount ? _(msg`Sign out`) : _(msg`Remove account`)}
variant="outline"
color="negative"
size="tiny"
style={[a.mr_md]}
onPress={onPressRemove}>
<ButtonText>
{isCurrentAccount ? <Trans>Sign out</Trans> : <Trans>Remove</Trans>}
</ButtonText>
</Button>
</AccountItemContent>
)
}
return (
<Button
testID={`chooseAccountBtn-${account.handle}`}
@@ -122,37 +167,61 @@ function AccountItem({
: _(msg`Sign in as ${account.handle}`)
}>
{({hovered, pressed}) => (
<View
style={[
a.flex_1,
a.flex_row,
a.align_center,
{height: 48},
(hovered || pressed || isPendingAccount) && t.atoms.bg_contrast_25,
]}>
<View style={a.p_md}>
<UserAvatar avatar={profile?.avatar} size={24} />
</View>
<Text style={[a.align_baseline, a.flex_1, a.flex_row, a.py_sm]}>
<Text emoji style={[a.font_bold]}>
{sanitizeDisplayName(
profile?.displayName || profile?.handle || account.handle,
)}
</Text>{' '}
<Text emoji style={[t.atoms.text_contrast_medium]}>
{sanitizeHandle(account.handle)}
</Text>
</Text>
<AccountItemContent
highlight={hovered || pressed || isPendingAccount}
profile={profile}
account={account}>
{isCurrentAccount ? (
<Check
<CheckIcon
size="sm"
style={[{color: t.palette.positive_600}, a.mr_md]}
/>
) : (
<Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
<ChevronIcon size="sm" style={[t.atoms.text, a.mr_md]} />
)}
</View>
</AccountItemContent>
)}
</Button>
)
}
function AccountItemContent({
highlight,
profile,
account,
children,
}: {
highlight?: boolean
profile?: AppBskyActorDefs.ProfileViewDetailed
account: SessionAccount
children?: React.ReactNode
}) {
const t = useTheme()
return (
<View
style={[
a.flex_1,
a.flex_row,
a.align_center,
{height: 48},
highlight && t.atoms.bg_contrast_25,
]}>
<View style={a.p_md}>
<UserAvatar avatar={profile?.avatar} size={24} />
</View>
<Text style={[a.align_baseline, a.flex_1, a.flex_row, a.py_sm]}>
<Text emoji style={[a.font_bold]}>
{sanitizeDisplayName(
profile?.displayName || profile?.handle || account.handle,
)}
</Text>{' '}
<Text emoji style={[t.atoms.text_contrast_medium]}>
{sanitizeHandle(account.handle)}
</Text>
</Text>
{children}
</View>
)
}
+1 -1
View File
@@ -9,7 +9,7 @@ import {
import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
export const Context = React.createContext<DialogContextProps>({
close: () => {},
close: cb => cb?.(),
isNativeDialog: false,
nativeSnapPoint: BottomSheetSnapPoint.Hidden,
disableDrag: false,
+27 -7
View File
@@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import React, {useCallback, useState} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -7,9 +7,10 @@ import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
import {type SessionAccount, useSession} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {atoms as a} from '#/alf'
import {AccountList} from '#/components/AccountList'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {AccountList} from '../AccountList'
import {Text} from '../Typography'
import {Text} from '#/components/Typography'
export function SwitchAccountDialog({
control,
@@ -20,6 +21,7 @@ export function SwitchAccountDialog({
const {currentAccount} = useSession()
const {onPressSwitchAccount, pendingDid} = useAccountSwitcher()
const {setShowLoggedOut} = useLoggedOutViewControls()
const [editing, setEditing] = useState(false)
const onSelectAccount = useCallback(
(account: SessionAccount) => {
@@ -44,16 +46,34 @@ export function SwitchAccountDialog({
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner label={_(msg`Switch Account`)}>
<View style={[a.gap_lg]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Trans>Switch Account</Trans>
</Text>
<View style={[a.gap_sm]}>
<View
style={[
a.flex_row,
a.justify_between,
a.align_center,
a.flex_wrap,
]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Trans>Switch account</Trans>
</Text>
<Button
label={editing ? _(msg`Done`) : _(msg`Edit`)}
onPress={() => setEditing(!editing)}
variant="ghost"
color="secondary"
size="small">
<ButtonText>{editing ? _(msg`Done`) : _(msg`Edit`)}</ButtonText>
</Button>
</View>
<AccountList
onSelectAccount={onSelectAccount}
onSelectOther={onPressAddAccount}
otherLabel={_(msg`Add account`)}
pendingDid={pendingDid}
editing={editing}
/>
</View>
</Dialog.ScrollableInner>
+6 -1
View File
@@ -13,7 +13,12 @@ export type LogEvents = {
withPassword: boolean
}
'account:loggedOut': {
logContext: 'SwitchAccount' | 'Settings' | 'SignupQueued' | 'Deactivated'
logContext:
| 'SwitchAccount'
| 'Settings'
| 'SignupQueued'
| 'Deactivated'
| 'AccountList'
scope: 'current' | 'every'
}
'notifications:openApp': {}
+9 -1
View File
@@ -10,6 +10,7 @@ import {
ComAtprotoRepoUploadBlob,
} from '@atproto/api'
import {
keepPreviousData,
QueryClient,
useMutation,
useQuery,
@@ -81,7 +82,13 @@ export function useProfileQuery({
})
}
export function useProfilesQuery({handles}: {handles: string[]}) {
export function useProfilesQuery({
handles,
keepPrevious,
}: {
handles: string[]
keepPrevious: boolean
}) {
const agent = useAgent()
return useQuery({
staleTime: STALE.MINUTES.FIVE,
@@ -90,6 +97,7 @@ export function useProfilesQuery({handles}: {handles: string[]}) {
const res = await agent.getProfiles({actors: handles})
return res.data
},
placeholderData: keepPrevious ? keepPreviousData : undefined,
})
}