Use ALF for the account quick switch dialog (#3327)
* Use ALF for account quick switch * clean up modal type * add haptics to dialog opening * move account list to it's own component and share * make tick slightly darkerzio/stable
parent
8cdd8394df
commit
712768dd8f
|
@ -0,0 +1,141 @@
|
||||||
|
import React, {useCallback} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
|
import {type SessionAccount, useSession} 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'
|
||||||
|
|
||||||
|
export function AccountList({
|
||||||
|
onSelectAccount,
|
||||||
|
onSelectOther,
|
||||||
|
otherLabel,
|
||||||
|
}: {
|
||||||
|
onSelectAccount: (account: SessionAccount) => void
|
||||||
|
onSelectOther: () => void
|
||||||
|
otherLabel?: string
|
||||||
|
}) {
|
||||||
|
const {isSwitchingAccounts, currentAccount, accounts} = useSession()
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
|
const onPressAddAccount = useCallback(() => {
|
||||||
|
onSelectOther()
|
||||||
|
}, [onSelectOther])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.rounded_md,
|
||||||
|
a.overflow_hidden,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
]}>
|
||||||
|
{accounts.map(account => (
|
||||||
|
<React.Fragment key={account.did}>
|
||||||
|
<AccountItem
|
||||||
|
account={account}
|
||||||
|
onSelect={onSelectAccount}
|
||||||
|
isCurrentAccount={account.did === currentAccount?.did}
|
||||||
|
/>
|
||||||
|
<View style={[a.border_b, t.atoms.border_contrast_low]} />
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
testID="chooseAddAccountBtn"
|
||||||
|
style={[a.flex_1]}
|
||||||
|
onPress={isSwitchingAccounts ? 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 || isSwitchingAccounts) &&
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.align_baseline,
|
||||||
|
a.flex_1,
|
||||||
|
a.flex_row,
|
||||||
|
a.py_sm,
|
||||||
|
{paddingLeft: 48},
|
||||||
|
]}>
|
||||||
|
{otherLabel ?? <Trans>Other account</Trans>}
|
||||||
|
</Text>
|
||||||
|
<Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountItem({
|
||||||
|
account,
|
||||||
|
onSelect,
|
||||||
|
isCurrentAccount,
|
||||||
|
}: {
|
||||||
|
account: SessionAccount
|
||||||
|
onSelect: (account: SessionAccount) => void
|
||||||
|
isCurrentAccount: boolean
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {data: profile} = useProfileQuery({did: account.did})
|
||||||
|
|
||||||
|
const onPress = React.useCallback(() => {
|
||||||
|
onSelect(account)
|
||||||
|
}, [account, onSelect])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
testID={`chooseAccountBtn-${account.handle}`}
|
||||||
|
key={account.did}
|
||||||
|
style={[a.flex_1]}
|
||||||
|
onPress={onPress}
|
||||||
|
label={
|
||||||
|
isCurrentAccount
|
||||||
|
? _(msg`Continue as ${account.handle} (currently signed in)`)
|
||||||
|
: _(msg`Sign in as ${account.handle}`)
|
||||||
|
}>
|
||||||
|
{({hovered, pressed}) => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
{height: 48},
|
||||||
|
(hovered || pressed) && 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 style={[a.font_bold]}>
|
||||||
|
{profile?.displayName || account.handle}{' '}
|
||||||
|
</Text>
|
||||||
|
<Text style={[t.atoms.text_contrast_medium]}>{account.handle}</Text>
|
||||||
|
</Text>
|
||||||
|
{isCurrentAccount ? (
|
||||||
|
<Check
|
||||||
|
size="sm"
|
||||||
|
style={[{color: t.palette.positive_600}, a.mr_md]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,23 +1,23 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import * as Dialog from '#/components/Dialog'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {Text} from '../Typography'
|
|
||||||
import {DateInput} from '#/view/com/util/forms/DateInput'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {isIOS, isWeb} from '#/platform/detection'
|
||||||
import {
|
import {
|
||||||
usePreferencesQuery,
|
usePreferencesQuery,
|
||||||
usePreferencesSetBirthDateMutation,
|
|
||||||
UsePreferencesQueryResponse,
|
UsePreferencesQueryResponse,
|
||||||
|
usePreferencesSetBirthDateMutation,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
import {Button, ButtonIcon, ButtonText} from '../Button'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {DateInput} from '#/view/com/util/forms/DateInput'
|
||||||
import {isIOS, isWeb} from '#/platform/detection'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '../Button'
|
||||||
|
import {Text} from '../Typography'
|
||||||
|
|
||||||
export function BirthDateSettingsDialog({
|
export function BirthDateSettingsDialog({
|
||||||
control,
|
control,
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React, {useCallback} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
|
||||||
|
import {type SessionAccount, useSession} from '#/state/session'
|
||||||
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
|
import {useCloseAllActiveElements} from '#/state/util'
|
||||||
|
import {atoms as a} from '#/alf'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {AccountList} from '../AccountList'
|
||||||
|
import {Text} from '../Typography'
|
||||||
|
|
||||||
|
export function SwitchAccountDialog({
|
||||||
|
control,
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const {onPressSwitchAccount} = useAccountSwitcher()
|
||||||
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
const closeAllActiveElements = useCloseAllActiveElements()
|
||||||
|
|
||||||
|
const onSelectAccount = useCallback(
|
||||||
|
(account: SessionAccount) => {
|
||||||
|
if (account.did === currentAccount?.did) {
|
||||||
|
control.close()
|
||||||
|
} else {
|
||||||
|
onPressSwitchAccount(account, 'SwitchAccount')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentAccount, control, onPressSwitchAccount],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onPressAddAccount = useCallback(() => {
|
||||||
|
setShowLoggedOut(true)
|
||||||
|
closeAllActiveElements()
|
||||||
|
}, [setShowLoggedOut, closeAllActiveElements])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<AccountList
|
||||||
|
onSelectAccount={onSelectAccount}
|
||||||
|
onSelectOther={onPressAddAccount}
|
||||||
|
otherLabel={_(msg`Add account`)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
|
@ -5,76 +5,15 @@ import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {logEvent} from '#/lib/statsig/statsig'
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {colors} from '#/lib/styles'
|
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
|
||||||
import {SessionAccount, useSession, useSessionApi} from '#/state/session'
|
import {SessionAccount, useSession, useSessionApi} from '#/state/session'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {atoms as a} from '#/alf'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {AccountList} from '#/components/AccountList'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
|
||||||
import {ChevronRight_Stroke2_Corner0_Rounded as Chevron} from '#/components/icons/Chevron'
|
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
|
|
||||||
function AccountItem({
|
|
||||||
account,
|
|
||||||
onSelect,
|
|
||||||
isCurrentAccount,
|
|
||||||
}: {
|
|
||||||
account: SessionAccount
|
|
||||||
onSelect: (account: SessionAccount) => void
|
|
||||||
isCurrentAccount: boolean
|
|
||||||
}) {
|
|
||||||
const t = useTheme()
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {data: profile} = useProfileQuery({did: account.did})
|
|
||||||
|
|
||||||
const onPress = React.useCallback(() => {
|
|
||||||
onSelect(account)
|
|
||||||
}, [account, onSelect])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
testID={`chooseAccountBtn-${account.handle}`}
|
|
||||||
key={account.did}
|
|
||||||
style={[a.flex_1]}
|
|
||||||
onPress={onPress}
|
|
||||||
label={
|
|
||||||
isCurrentAccount
|
|
||||||
? _(msg`Continue as ${account.handle} (currently signed in)`)
|
|
||||||
: _(msg`Sign in as ${account.handle}`)
|
|
||||||
}>
|
|
||||||
{({hovered, pressed}) => (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
{height: 48},
|
|
||||||
(hovered || pressed) && 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 style={[a.font_bold]}>
|
|
||||||
{profile?.displayName || account.handle}{' '}
|
|
||||||
</Text>
|
|
||||||
<Text style={[t.atoms.text_contrast_medium]}>{account.handle}</Text>
|
|
||||||
</Text>
|
|
||||||
{isCurrentAccount ? (
|
|
||||||
<Check size="sm" style={[{color: colors.green3}, a.mr_md]} />
|
|
||||||
) : (
|
|
||||||
<Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export const ChooseAccountForm = ({
|
export const ChooseAccountForm = ({
|
||||||
onSelectAccount,
|
onSelectAccount,
|
||||||
onPressBack,
|
onPressBack,
|
||||||
|
@ -84,8 +23,7 @@ export const ChooseAccountForm = ({
|
||||||
}) => {
|
}) => {
|
||||||
const {track, screen} = useAnalytics()
|
const {track, screen} = useAnalytics()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const {currentAccount} = useSession()
|
||||||
const {accounts, currentAccount} = useSession()
|
|
||||||
const {initSession} = useSessionApi()
|
const {initSession} = useSessionApi()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
|
||||||
|
@ -125,52 +63,10 @@ export const ChooseAccountForm = ({
|
||||||
<TextField.Label>
|
<TextField.Label>
|
||||||
<Trans>Sign in as...</Trans>
|
<Trans>Sign in as...</Trans>
|
||||||
</TextField.Label>
|
</TextField.Label>
|
||||||
<View
|
<AccountList
|
||||||
style={[
|
onSelectAccount={onSelect}
|
||||||
a.rounded_md,
|
onSelectOther={() => onSelectAccount()}
|
||||||
a.overflow_hidden,
|
|
||||||
a.border,
|
|
||||||
t.atoms.border_contrast_low,
|
|
||||||
]}>
|
|
||||||
{accounts.map(account => (
|
|
||||||
<React.Fragment key={account.did}>
|
|
||||||
<AccountItem
|
|
||||||
account={account}
|
|
||||||
onSelect={onSelect}
|
|
||||||
isCurrentAccount={account.did === currentAccount?.did}
|
|
||||||
/>
|
/>
|
||||||
<View style={[a.border_b, t.atoms.border_contrast_low]} />
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
testID="chooseNewAccountBtn"
|
|
||||||
style={[a.flex_1]}
|
|
||||||
onPress={() => onSelectAccount(undefined)}
|
|
||||||
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
|
|
||||||
style={[
|
|
||||||
a.align_baseline,
|
|
||||||
a.flex_1,
|
|
||||||
a.flex_row,
|
|
||||||
a.py_sm,
|
|
||||||
{paddingLeft: 48},
|
|
||||||
]}>
|
|
||||||
<Trans>Other account</Trans>
|
|
||||||
</Text>
|
|
||||||
<Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[a.flex_row]}>
|
<View style={[a.flex_row]}>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||||
|
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
||||||
|
|
||||||
import {ImageModel} from '#/state/models/media/image'
|
|
||||||
import {GalleryModel} from '#/state/models/media/gallery'
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {EmbedPlayerSource} from '#/lib/strings/embed-player'
|
import {EmbedPlayerSource} from '#/lib/strings/embed-player'
|
||||||
|
import {GalleryModel} from '#/state/models/media/gallery'
|
||||||
|
import {ImageModel} from '#/state/models/media/image'
|
||||||
import {ThreadgateSetting} from '../queries/threadgate'
|
import {ThreadgateSetting} from '../queries/threadgate'
|
||||||
|
|
||||||
export interface EditProfileModal {
|
export interface EditProfileModal {
|
||||||
|
@ -118,10 +118,6 @@ export interface ChangePasswordModal {
|
||||||
name: 'change-password'
|
name: 'change-password'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SwitchAccountModal {
|
|
||||||
name: 'switch-account'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LinkWarningModal {
|
export interface LinkWarningModal {
|
||||||
name: 'link-warning'
|
name: 'link-warning'
|
||||||
text: string
|
text: string
|
||||||
|
@ -148,7 +144,6 @@ export type Modal =
|
||||||
| VerifyEmailModal
|
| VerifyEmailModal
|
||||||
| ChangeEmailModal
|
| ChangeEmailModal
|
||||||
| ChangePasswordModal
|
| ChangePasswordModal
|
||||||
| SwitchAccountModal
|
|
||||||
|
|
||||||
// Curation
|
// Curation
|
||||||
| ContentLanguagesSettingsModal
|
| ContentLanguagesSettingsModal
|
||||||
|
|
|
@ -24,7 +24,6 @@ import * as LinkWarningModal from './LinkWarning'
|
||||||
import * as ListAddUserModal from './ListAddRemoveUsers'
|
import * as ListAddUserModal from './ListAddRemoveUsers'
|
||||||
import * as RepostModal from './Repost'
|
import * as RepostModal from './Repost'
|
||||||
import * as SelfLabelModal from './SelfLabel'
|
import * as SelfLabelModal from './SelfLabel'
|
||||||
import * as SwitchAccountModal from './SwitchAccount'
|
|
||||||
import * as ThreadgateModal from './Threadgate'
|
import * as ThreadgateModal from './Threadgate'
|
||||||
import * as UserAddRemoveListsModal from './UserAddRemoveLists'
|
import * as UserAddRemoveListsModal from './UserAddRemoveLists'
|
||||||
import * as VerifyEmailModal from './VerifyEmail'
|
import * as VerifyEmailModal from './VerifyEmail'
|
||||||
|
@ -114,9 +113,6 @@ export function ModalsContainer() {
|
||||||
} else if (activeModal?.name === 'change-password') {
|
} else if (activeModal?.name === 'change-password') {
|
||||||
snapPoints = ChangePasswordModal.snapPoints
|
snapPoints = ChangePasswordModal.snapPoints
|
||||||
element = <ChangePasswordModal.Component />
|
element = <ChangePasswordModal.Component />
|
||||||
} else if (activeModal?.name === 'switch-account') {
|
|
||||||
snapPoints = SwitchAccountModal.snapPoints
|
|
||||||
element = <SwitchAccountModal.Component />
|
|
||||||
} else if (activeModal?.name === 'link-warning') {
|
} else if (activeModal?.name === 'link-warning') {
|
||||||
snapPoints = LinkWarningModal.snapPoints
|
snapPoints = LinkWarningModal.snapPoints
|
||||||
element = <LinkWarningModal.Component {...activeModal} />
|
element = <LinkWarningModal.Component {...activeModal} />
|
||||||
|
|
|
@ -1,169 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
ActivityIndicator,
|
|
||||||
StyleSheet,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {BottomSheetScrollView} from '@discord/bottom-sheet/src'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
|
||||||
import {SessionAccount, useSession, useSessionApi} from '#/state/session'
|
|
||||||
import {useCloseAllActiveElements} from '#/state/util'
|
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
|
||||||
import {Haptics} from 'lib/haptics'
|
|
||||||
import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {makeProfileLink} from 'lib/routes/links'
|
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {AccountDropdownBtn} from '../util/AccountDropdownBtn'
|
|
||||||
import {Link} from '../util/Link'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
|
||||||
|
|
||||||
export const snapPoints = ['40%', '90%']
|
|
||||||
|
|
||||||
function SwitchAccountCard({account}: {account: SessionAccount}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {track} = useAnalytics()
|
|
||||||
const {isSwitchingAccounts, currentAccount} = useSession()
|
|
||||||
const {logout} = useSessionApi()
|
|
||||||
const {data: profile} = useProfileQuery({did: account.did})
|
|
||||||
const isCurrentAccount = account.did === currentAccount?.did
|
|
||||||
const {onPressSwitchAccount} = useAccountSwitcher()
|
|
||||||
const closeAllActiveElements = useCloseAllActiveElements()
|
|
||||||
|
|
||||||
const onPressSignout = React.useCallback(() => {
|
|
||||||
track('Settings:SignOutButtonClicked')
|
|
||||||
closeAllActiveElements()
|
|
||||||
// needs to be in timeout or the modal re-opens
|
|
||||||
setTimeout(() => logout('SwitchAccount'), 0)
|
|
||||||
}, [track, logout, closeAllActiveElements])
|
|
||||||
|
|
||||||
const contents = (
|
|
||||||
<View style={[pal.view, styles.linkCard]}>
|
|
||||||
<View style={styles.avi}>
|
|
||||||
<UserAvatar
|
|
||||||
size={40}
|
|
||||||
avatar={profile?.avatar}
|
|
||||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={[s.flex1]}>
|
|
||||||
<Text type="md-bold" style={pal.text} numberOfLines={1}>
|
|
||||||
{profile?.displayName || account?.handle}
|
|
||||||
</Text>
|
|
||||||
<Text type="sm" style={pal.textLight} numberOfLines={1}>
|
|
||||||
{account?.handle}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{isCurrentAccount ? (
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="signOutBtn"
|
|
||||||
onPress={isSwitchingAccounts ? undefined : onPressSignout}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Sign out`)}
|
|
||||||
accessibilityHint={_(
|
|
||||||
msg`Signs ${profile?.displayName} out of Bluesky`,
|
|
||||||
)}>
|
|
||||||
<Text type="lg" style={pal.link}>
|
|
||||||
<Trans>Sign out</Trans>
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
) : (
|
|
||||||
<AccountDropdownBtn account={account} />
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
|
|
||||||
return isCurrentAccount ? (
|
|
||||||
<Link
|
|
||||||
href={makeProfileLink({
|
|
||||||
did: currentAccount.did,
|
|
||||||
handle: currentAccount.handle,
|
|
||||||
})}
|
|
||||||
title={_(msg`Your profile`)}
|
|
||||||
noFeedback>
|
|
||||||
{contents}
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<TouchableOpacity
|
|
||||||
testID={`switchToAccountBtn-${account.handle}`}
|
|
||||||
key={account.did}
|
|
||||||
style={[isSwitchingAccounts && styles.dimmed]}
|
|
||||||
onPress={
|
|
||||||
isSwitchingAccounts
|
|
||||||
? undefined
|
|
||||||
: () => onPressSwitchAccount(account, 'SwitchAccount')
|
|
||||||
}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Switch to ${account.handle}`)}
|
|
||||||
accessibilityHint={_(msg`Switches the account you are logged in to`)}>
|
|
||||||
{contents}
|
|
||||||
</TouchableOpacity>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Component({}: {}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {isSwitchingAccounts, currentAccount, accounts} = useSession()
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
Haptics.default()
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BottomSheetScrollView
|
|
||||||
style={[styles.container, pal.view]}
|
|
||||||
contentContainerStyle={[styles.innerContainer, pal.view]}>
|
|
||||||
<Text type="title-xl" style={[styles.title, pal.text]}>
|
|
||||||
<Trans>Switch Account</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{isSwitchingAccounts || !currentAccount ? (
|
|
||||||
<View style={[pal.view, styles.linkCard]}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<SwitchAccountCard account={currentAccount} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{accounts
|
|
||||||
.filter(a => a.did !== currentAccount?.did)
|
|
||||||
.map(account => (
|
|
||||||
<SwitchAccountCard key={account.did} account={account} />
|
|
||||||
))}
|
|
||||||
</BottomSheetScrollView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
innerContainer: {
|
|
||||||
paddingBottom: 40,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: 12,
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
linkCard: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: 12,
|
|
||||||
paddingHorizontal: 18,
|
|
||||||
marginBottom: 1,
|
|
||||||
},
|
|
||||||
avi: {
|
|
||||||
marginRight: 12,
|
|
||||||
},
|
|
||||||
dimmed: {
|
|
||||||
opacity: 0.5,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,47 +1,49 @@
|
||||||
import React, {ComponentProps} from 'react'
|
import React, {ComponentProps} from 'react'
|
||||||
import {GestureResponderEvent, TouchableOpacity, View} from 'react-native'
|
import {GestureResponderEvent, TouchableOpacity, View} from 'react-native'
|
||||||
import Animated from 'react-native-reanimated'
|
import Animated from 'react-native-reanimated'
|
||||||
import {StackActions} from '@react-navigation/native'
|
|
||||||
import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
|
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useLingui} from '@lingui/react'
|
||||||
import {clamp} from 'lib/numbers'
|
import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
|
||||||
|
import {StackActions} from '@react-navigation/native'
|
||||||
|
|
||||||
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
|
import {Haptics} from '#/lib/haptics'
|
||||||
|
import {useDedupe} from '#/lib/hooks/useDedupe'
|
||||||
|
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
|
||||||
|
import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {
|
import {
|
||||||
|
BellIcon,
|
||||||
|
BellIconSolid,
|
||||||
|
HashtagIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
HomeIconSolid,
|
HomeIconSolid,
|
||||||
MagnifyingGlassIcon2,
|
MagnifyingGlassIcon2,
|
||||||
MagnifyingGlassIcon2Solid,
|
MagnifyingGlassIcon2Solid,
|
||||||
HashtagIcon,
|
} from '#/lib/icons'
|
||||||
BellIcon,
|
import {clamp} from '#/lib/numbers'
|
||||||
BellIconSolid,
|
import {getTabState, TabState} from '#/lib/routes/helpers'
|
||||||
} from 'lib/icons'
|
import {s} from '#/lib/styles'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {getTabState, TabState} from 'lib/routes/helpers'
|
|
||||||
import {styles} from './BottomBarStyles'
|
|
||||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
|
||||||
import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
|
|
||||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
|
||||||
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
|
||||||
import {emitSoftReset} from '#/state/events'
|
import {emitSoftReset} from '#/state/events'
|
||||||
import {useSession} from '#/state/session'
|
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
import {useCloseAllActiveElements} from '#/state/util'
|
import {useCloseAllActiveElements} from '#/state/util'
|
||||||
import {Button} from '#/view/com/util/forms/Button'
|
import {Button} from '#/view/com/util/forms/Button'
|
||||||
import {s} from 'lib/styles'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {Logotype} from '#/view/icons/Logotype'
|
import {Logotype} from '#/view/icons/Logotype'
|
||||||
import {useDedupe} from 'lib/hooks/useDedupe'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount'
|
||||||
|
import {styles} from './BottomBarStyles'
|
||||||
|
|
||||||
type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds'
|
type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds'
|
||||||
|
|
||||||
export function BottomBar({navigation}: BottomTabBarProps) {
|
export function BottomBar({navigation}: BottomTabBarProps) {
|
||||||
const {openModal} = useModalControls()
|
|
||||||
const {hasSession, currentAccount} = useSession()
|
const {hasSession, currentAccount} = useSession()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
@ -56,6 +58,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||||
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
||||||
const closeAllActiveElements = useCloseAllActiveElements()
|
const closeAllActiveElements = useCloseAllActiveElements()
|
||||||
const dedupe = useDedupe()
|
const dedupe = useDedupe()
|
||||||
|
const accountSwitchControl = useDialogControl()
|
||||||
|
|
||||||
const showSignIn = React.useCallback(() => {
|
const showSignIn = React.useCallback(() => {
|
||||||
closeAllActiveElements()
|
closeAllActiveElements()
|
||||||
|
@ -99,11 +102,16 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||||
const onPressProfile = React.useCallback(() => {
|
const onPressProfile = React.useCallback(() => {
|
||||||
onPressTab('MyProfile')
|
onPressTab('MyProfile')
|
||||||
}, [onPressTab])
|
}, [onPressTab])
|
||||||
|
|
||||||
const onLongPressProfile = React.useCallback(() => {
|
const onLongPressProfile = React.useCallback(() => {
|
||||||
openModal({name: 'switch-account'})
|
Haptics.default()
|
||||||
}, [openModal])
|
accountSwitchControl.open()
|
||||||
|
}, [accountSwitchControl])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<SwitchAccountDialog control={accountSwitchControl} />
|
||||||
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
styles.bottomBar,
|
styles.bottomBar,
|
||||||
|
@ -233,7 +241,8 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View style={[styles.ctrlIcon, pal.text, styles.profileIcon]}>
|
<View
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.profileIcon]}>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
avatar={profile?.avatar}
|
avatar={profile?.avatar}
|
||||||
size={28}
|
size={28}
|
||||||
|
@ -266,14 +275,16 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||||
paddingRight: 6,
|
paddingRight: 6,
|
||||||
gap: 8,
|
gap: 8,
|
||||||
}}>
|
}}>
|
||||||
<View style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
|
<View
|
||||||
|
style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
|
||||||
<Logo width={28} />
|
<Logo width={28} />
|
||||||
<View style={{paddingTop: 4}}>
|
<View style={{paddingTop: 4}}>
|
||||||
<Logotype width={80} fill={pal.text.color} />
|
<Logotype width={80} fill={pal.text.color} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
|
<View
|
||||||
|
style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
|
||||||
<Button
|
<Button
|
||||||
onPress={showCreateAccount}
|
onPress={showCreateAccount}
|
||||||
accessibilityHint={_(msg`Sign up`)}
|
accessibilityHint={_(msg`Sign up`)}
|
||||||
|
@ -297,6 +308,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue