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