[Settings] Improved account switcher (#6131)
* move out avatarstack to own file * improved settings switch * prefix with @ * fix types * up chevron * respect reduced motion setting * respect reduced motion in other place
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM12 14c-2.95 0-5.163 1.733-6.08 4.21a.47.47 0 0 0 .09.493.9.9 0 0 0 .687.297H11a1 1 0 1 1 0 2H6.697a2.9 2.9 0 0 1-2.219-1.011 2.46 2.46 0 0 1-.433-2.473C5.235 14.296 8.168 12 12 12c.787 0 1.54.097 2.252.282a1 1 0 1 1-.504 1.936A7 7 0 0 0 12 14Zm6 0a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 569 B |
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {moderateProfile} from '@atproto/api'
|
||||||
|
|
||||||
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
|
import {useProfilesQuery} from '#/state/queries/profile'
|
||||||
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
|
||||||
|
export function AvatarStack({
|
||||||
|
profiles,
|
||||||
|
size = 26,
|
||||||
|
}: {
|
||||||
|
profiles: string[]
|
||||||
|
size?: number
|
||||||
|
}) {
|
||||||
|
const halfSize = size / 2
|
||||||
|
const {data, error} = useProfilesQuery({handles: profiles})
|
||||||
|
const t = useTheme()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPending = !data || !moderationOpts
|
||||||
|
|
||||||
|
const items = isPending
|
||||||
|
? Array.from({length: profiles.length}).map((_, i) => ({
|
||||||
|
key: i,
|
||||||
|
profile: null,
|
||||||
|
moderation: null,
|
||||||
|
}))
|
||||||
|
: data.profiles.map(item => ({
|
||||||
|
key: item.did,
|
||||||
|
profile: item,
|
||||||
|
moderation: moderateProfile(item, moderationOpts),
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.relative,
|
||||||
|
{width: size + (items.length - 1) * halfSize},
|
||||||
|
]}>
|
||||||
|
{items.map((item, i) => (
|
||||||
|
<View
|
||||||
|
key={item.key}
|
||||||
|
style={[
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
a.relative,
|
||||||
|
{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
left: i * -halfSize,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: t.atoms.bg.backgroundColor,
|
||||||
|
borderRadius: 999,
|
||||||
|
zIndex: 3 - i,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{item.profile && (
|
||||||
|
<UserAvatar
|
||||||
|
size={size - 2}
|
||||||
|
avatar={item.profile.avatar}
|
||||||
|
moderation={item.moderation.ui('avatar')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -24,6 +24,10 @@ export const PersonPlus_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
|||||||
path: 'M7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H15a3 3 0 1 1 0-6c0-.824.332-1.571.87-2.113C14.739 12.32 13.435 12 12 12Zm6 2a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z',
|
path: 'M7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H15a3 3 0 1 1 0-6c0-.824.332-1.571.87-2.113C14.739 12.32 13.435 12 12 12Zm6 2a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const PersonPlus_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM12 14c-2.95 0-5.163 1.733-6.08 4.21a.47.47 0 0 0 .09.493.9.9 0 0 0 .687.297H11a1 1 0 1 1 0 2H6.697a2.9 2.9 0 0 1-2.219-1.011 2.46 2.46 0 0 1-.433-2.473C5.235 14.296 8.168 12 12 12c.787 0 1.54.097 2.252.282a1 1 0 1 1-.504 1.936A7 7 0 0 0 12 14Zm6 0a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z',
|
||||||
|
})
|
||||||
|
|
||||||
export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||||
path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z',
|
path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z',
|
||||||
})
|
})
|
||||||
|
|||||||
+192
-104
@@ -1,6 +1,7 @@
|
|||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {LayoutAnimation, View} from 'react-native'
|
import {LayoutAnimation, Pressable, View} from 'react-native'
|
||||||
import {Linking} from 'react-native'
|
import {Linking} from 'react-native'
|
||||||
|
import {useReducedMotion} from 'react-native-reanimated'
|
||||||
import {AppBskyActorDefs, moderateProfile} from '@atproto/api'
|
import {AppBskyActorDefs, moderateProfile} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -9,13 +10,15 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
|||||||
|
|
||||||
import {IS_INTERNAL} from '#/lib/app-info'
|
import {IS_INTERNAL} from '#/lib/app-info'
|
||||||
import {HELP_DESK_URL} from '#/lib/constants'
|
import {HELP_DESK_URL} from '#/lib/constants'
|
||||||
|
import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
|
||||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||||
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {clearStorage} from '#/state/persisted'
|
import {clearStorage} from '#/state/persisted'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration'
|
import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration'
|
||||||
import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile'
|
import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile'
|
||||||
import {useSession, useSessionApi} from '#/state/session'
|
import {SessionAccount, useSession, useSessionApi} from '#/state/session'
|
||||||
import {useOnboardingDispatch} from '#/state/shell'
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {useCloseAllActiveElements} from '#/state/util'
|
import {useCloseAllActiveElements} from '#/state/util'
|
||||||
@@ -24,42 +27,50 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
|
|||||||
import {ProfileHeaderDisplayName} from '#/screens/Profile/Header/DisplayName'
|
import {ProfileHeaderDisplayName} from '#/screens/Profile/Header/DisplayName'
|
||||||
import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle'
|
import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle'
|
||||||
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, tokens, useTheme} from '#/alf'
|
||||||
|
import {AvatarStack} from '#/components/AvatarStack'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount'
|
import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount'
|
||||||
import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility'
|
import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility'
|
||||||
import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo'
|
import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo'
|
||||||
|
import {ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon} from '#/components/icons/Chevron'
|
||||||
import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion'
|
import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion'
|
||||||
import {CodeBrackets_Stroke2_Corner2_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets'
|
import {CodeBrackets_Stroke2_Corner2_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets'
|
||||||
|
import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
|
||||||
import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe'
|
import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe'
|
||||||
import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock'
|
import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock'
|
||||||
import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller'
|
import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller'
|
||||||
import {
|
import {
|
||||||
Person_Stroke2_Corner2_Rounded as PersonIcon,
|
Person_Stroke2_Corner2_Rounded as PersonIcon,
|
||||||
PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon,
|
PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon,
|
||||||
|
PersonPlus_Stroke2_Corner2_Rounded as PersonPlusIcon,
|
||||||
|
PersonX_Stroke2_Corner0_Rounded as PersonXIcon,
|
||||||
} from '#/components/icons/Person'
|
} from '#/components/icons/Person'
|
||||||
import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand'
|
import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand'
|
||||||
import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window'
|
import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import * as Menu from '#/components/Menu'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
|
||||||
export function SettingsScreen({}: Props) {
|
export function SettingsScreen({}: Props) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const reducedMotion = useReducedMotion()
|
||||||
const {logoutEveryAccount} = useSessionApi()
|
const {logoutEveryAccount} = useSessionApi()
|
||||||
const {accounts, currentAccount} = useSession()
|
const {accounts, currentAccount} = useSession()
|
||||||
const switchAccountControl = useDialogControl()
|
const switchAccountControl = useDialogControl()
|
||||||
const signOutPromptControl = Prompt.usePromptControl()
|
const signOutPromptControl = Prompt.usePromptControl()
|
||||||
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {data: otherProfiles} = useProfilesQuery({
|
||||||
const closeEverything = useCloseAllActiveElements()
|
handles: accounts
|
||||||
|
.filter(acc => acc.did !== currentAccount?.did)
|
||||||
|
.map(acc => acc.handle),
|
||||||
|
})
|
||||||
|
const {pendingDid, onPressSwitchAccount} = useAccountSwitcher()
|
||||||
|
const [showAccounts, setShowAccounts] = useState(false)
|
||||||
const [showDevOptions, setShowDevOptions] = useState(false)
|
const [showDevOptions, setShowDevOptions] = useState(false)
|
||||||
|
|
||||||
const onAddAnotherAccount = () => {
|
|
||||||
setShowLoggedOut(true)
|
|
||||||
closeEverything()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_(msg`Settings`)} />
|
<Layout.Header title={_(msg`Settings`)} />
|
||||||
@@ -77,34 +88,59 @@ export function SettingsScreen({}: Props) {
|
|||||||
]}>
|
]}>
|
||||||
{profile && <ProfilePreview profile={profile} />}
|
{profile && <ProfilePreview profile={profile} />}
|
||||||
</View>
|
</View>
|
||||||
<SettingsList.PressableItem
|
{accounts.length > 1 ? (
|
||||||
label={
|
<>
|
||||||
accounts.length > 1
|
<SettingsList.PressableItem
|
||||||
? _(msg`Switch account`)
|
label={_(msg`Switch account`)}
|
||||||
: _(msg`Add another account`)
|
accessibilityHint={_(
|
||||||
}
|
msg`Show other accounts you can switch to`,
|
||||||
onPress={() =>
|
)}
|
||||||
accounts.length > 1
|
onPress={() => {
|
||||||
? switchAccountControl.open()
|
if (!reducedMotion) {
|
||||||
: onAddAnotherAccount()
|
LayoutAnimation.configureNext(
|
||||||
}>
|
LayoutAnimation.Presets.easeInEaseOut,
|
||||||
<SettingsList.ItemIcon icon={PersonGroupIcon} />
|
)
|
||||||
<SettingsList.ItemText>
|
}
|
||||||
{accounts.length > 1 ? (
|
setShowAccounts(s => !s)
|
||||||
<Trans>Switch account</Trans>
|
}}>
|
||||||
) : (
|
<SettingsList.ItemIcon icon={PersonGroupIcon} />
|
||||||
<Trans>Add another account</Trans>
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Switch account</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
{showAccounts ? (
|
||||||
|
<SettingsList.ItemIcon icon={ChevronUpIcon} size="md" />
|
||||||
|
) : (
|
||||||
|
<AvatarStack
|
||||||
|
profiles={accounts
|
||||||
|
.map(acc => acc.did)
|
||||||
|
.filter(did => did !== currentAccount?.did)
|
||||||
|
.slice(0, 5)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SettingsList.PressableItem>
|
||||||
|
{showAccounts && (
|
||||||
|
<>
|
||||||
|
<SettingsList.Divider />
|
||||||
|
{accounts
|
||||||
|
.filter(acc => acc.did !== currentAccount?.did)
|
||||||
|
.map(account => (
|
||||||
|
<AccountRow
|
||||||
|
key={account.did}
|
||||||
|
account={account}
|
||||||
|
profile={otherProfiles?.profiles?.find(
|
||||||
|
p => p.did === account.did,
|
||||||
|
)}
|
||||||
|
pendingDid={pendingDid}
|
||||||
|
onPressSwitchAccount={onPressSwitchAccount}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<AddAccountRow />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</SettingsList.ItemText>
|
</>
|
||||||
{accounts.length > 1 && (
|
) : (
|
||||||
<AvatarStack
|
<AddAccountRow />
|
||||||
profiles={accounts
|
)}
|
||||||
.map(acc => acc.did)
|
|
||||||
.filter(did => did !== currentAccount?.did)
|
|
||||||
.slice(0, 5)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</SettingsList.PressableItem>
|
|
||||||
<SettingsList.Divider />
|
<SettingsList.Divider />
|
||||||
<SettingsList.LinkItem to="/settings/account" label={_(msg`Account`)}>
|
<SettingsList.LinkItem to="/settings/account" label={_(msg`Account`)}>
|
||||||
<SettingsList.ItemIcon icon={PersonIcon} />
|
<SettingsList.ItemIcon icon={PersonIcon} />
|
||||||
@@ -188,9 +224,11 @@ export function SettingsScreen({}: Props) {
|
|||||||
<SettingsList.Divider />
|
<SettingsList.Divider />
|
||||||
<SettingsList.PressableItem
|
<SettingsList.PressableItem
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
LayoutAnimation.configureNext(
|
if (!reducedMotion) {
|
||||||
LayoutAnimation.Presets.easeInEaseOut,
|
LayoutAnimation.configureNext(
|
||||||
)
|
LayoutAnimation.Presets.easeInEaseOut,
|
||||||
|
)
|
||||||
|
}
|
||||||
setShowDevOptions(d => !d)
|
setShowDevOptions(d => !d)
|
||||||
}}
|
}}
|
||||||
label={_(msg`Developer options`)}>
|
label={_(msg`Developer options`)}>
|
||||||
@@ -245,70 +283,6 @@ function ProfilePreview({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVI_SIZE = 26
|
|
||||||
const HALF_AVI_SIZE = AVI_SIZE / 2
|
|
||||||
|
|
||||||
function AvatarStack({profiles}: {profiles: string[]}) {
|
|
||||||
const {data, error} = useProfilesQuery({handles: profiles})
|
|
||||||
const t = useTheme()
|
|
||||||
const moderationOpts = useModerationOpts()
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error(error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPending = !data || !moderationOpts
|
|
||||||
|
|
||||||
const items = isPending
|
|
||||||
? Array.from({length: profiles.length}).map((_, i) => ({
|
|
||||||
key: i,
|
|
||||||
profile: null,
|
|
||||||
moderation: null,
|
|
||||||
}))
|
|
||||||
: data.profiles.map(item => ({
|
|
||||||
key: item.did,
|
|
||||||
profile: item,
|
|
||||||
moderation: moderateProfile(item, moderationOpts),
|
|
||||||
}))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.relative,
|
|
||||||
{width: AVI_SIZE + (items.length - 1) * HALF_AVI_SIZE},
|
|
||||||
]}>
|
|
||||||
{items.map((item, i) => (
|
|
||||||
<View
|
|
||||||
key={item.key}
|
|
||||||
style={[
|
|
||||||
t.atoms.bg_contrast_25,
|
|
||||||
a.relative,
|
|
||||||
{
|
|
||||||
width: AVI_SIZE,
|
|
||||||
height: AVI_SIZE,
|
|
||||||
left: i * -HALF_AVI_SIZE,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: t.atoms.bg.backgroundColor,
|
|
||||||
borderRadius: 999,
|
|
||||||
zIndex: 3 - i,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
{item.profile && (
|
|
||||||
<UserAvatar
|
|
||||||
size={AVI_SIZE - 2}
|
|
||||||
avatar={item.profile.avatar}
|
|
||||||
moderation={item.moderation.ui('avatar')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DevOptions() {
|
function DevOptions() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const onboardingDispatch = useOnboardingDispatch()
|
const onboardingDispatch = useOnboardingDispatch()
|
||||||
@@ -373,3 +347,117 @@ function DevOptions() {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AddAccountRow() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
const closeEverything = useCloseAllActiveElements()
|
||||||
|
|
||||||
|
const onAddAnotherAccount = () => {
|
||||||
|
setShowLoggedOut(true)
|
||||||
|
closeEverything()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsList.PressableItem
|
||||||
|
onPress={onAddAnotherAccount}
|
||||||
|
label={_(msg`Add another account`)}>
|
||||||
|
<SettingsList.ItemIcon icon={PersonPlusIcon} />
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Add another account</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
</SettingsList.PressableItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountRow({
|
||||||
|
profile,
|
||||||
|
account,
|
||||||
|
pendingDid,
|
||||||
|
onPressSwitchAccount,
|
||||||
|
}: {
|
||||||
|
profile?: AppBskyActorDefs.ProfileViewDetailed
|
||||||
|
account: SessionAccount
|
||||||
|
pendingDid: string | null
|
||||||
|
onPressSwitchAccount: (
|
||||||
|
account: SessionAccount,
|
||||||
|
logContext: 'Settings',
|
||||||
|
) => void
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
const removePromptControl = Prompt.usePromptControl()
|
||||||
|
const {removeAccount} = useSessionApi()
|
||||||
|
|
||||||
|
const onSwitchAccount = () => {
|
||||||
|
if (pendingDid) return
|
||||||
|
onPressSwitchAccount(account, 'Settings')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.relative]}>
|
||||||
|
<SettingsList.PressableItem
|
||||||
|
onPress={onSwitchAccount}
|
||||||
|
label={_(msg`Switch account`)}>
|
||||||
|
{moderationOpts && profile ? (
|
||||||
|
<UserAvatar
|
||||||
|
size={28}
|
||||||
|
avatar={profile.avatar}
|
||||||
|
moderation={moderateProfile(profile, moderationOpts).ui('avatar')}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<View style={[{width: 28}]} />
|
||||||
|
)}
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>{sanitizeHandle(account.handle, '@')}</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
{pendingDid === account.did && <SettingsList.ItemIcon icon={Loader} />}
|
||||||
|
</SettingsList.PressableItem>
|
||||||
|
{!pendingDid && (
|
||||||
|
<Menu.Root>
|
||||||
|
<Menu.Trigger label={_(msg`Account options`)}>
|
||||||
|
{({props, state}) => (
|
||||||
|
<Pressable
|
||||||
|
{...props}
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
{top: 10, right: tokens.space.lg},
|
||||||
|
a.p_xs,
|
||||||
|
a.rounded_full,
|
||||||
|
(state.hovered || state.pressed) && t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
<DotsHorizontal size="md" style={t.atoms.text} />
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
|
</Menu.Trigger>
|
||||||
|
<Menu.Outer showCancel>
|
||||||
|
<Menu.Item
|
||||||
|
label={_(msg`Remove account`)}
|
||||||
|
onPress={() => removePromptControl.open()}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
<Trans>Remove account</Trans>
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={PersonXIcon} />
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Outer>
|
||||||
|
</Menu.Root>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Prompt.Basic
|
||||||
|
control={removePromptControl}
|
||||||
|
title={_(msg`Remove from quick access?`)}
|
||||||
|
description={_(
|
||||||
|
msg`This will remove @${account.handle} from the quick access list.`,
|
||||||
|
)}
|
||||||
|
onConfirm={() => {
|
||||||
|
removeAccount(account)
|
||||||
|
Toast.show(_(msg`Account removed from quick access`))
|
||||||
|
}}
|
||||||
|
confirmButtonCta={_(msg`Remove`)}
|
||||||
|
confirmButtonColor="negative"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user