about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2024-04-04 03:18:14 +0100
committerGitHub <noreply@github.com>2024-04-03 19:18:14 -0700
commit712768dd8f9172ff79700765f9f09db07ca00028 (patch)
tree5836e7512b516ab14536dd025b09bc099becb8ef
parent8cdd8394df52827a5880074e5cf11d5b62521249 (diff)
downloadvoidsky-712768dd8f9172ff79700765f9f09db07ca00028.tar.zst
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 darker
-rw-r--r--src/components/AccountList.tsx141
-rw-r--r--src/components/dialogs/BirthDateSettings.tsx20
-rw-r--r--src/components/dialogs/SwitchAccount.tsx61
-rw-r--r--src/screens/Login/ChooseAccountForm.tsx118
-rw-r--r--src/state/modals/index.tsx11
-rw-r--r--src/view/com/modals/Modal.tsx4
-rw-r--r--src/view/com/modals/SwitchAccount.tsx169
-rw-r--r--src/view/shell/bottom-bar/BottomBar.tsx440
8 files changed, 448 insertions, 516 deletions
diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx
new file mode 100644
index 000000000..169e7b84f
--- /dev/null
+++ b/src/components/AccountList.tsx
@@ -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>
+  )
+}
diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx
index 4a3e96e56..d831c6002 100644
--- a/src/components/dialogs/BirthDateSettings.tsx
+++ b/src/components/dialogs/BirthDateSettings.tsx
@@ -1,23 +1,23 @@
 import React from 'react'
-import {useLingui} from '@lingui/react'
-import {Trans, msg} from '@lingui/macro'
 import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 
-import * as Dialog from '#/components/Dialog'
-import {Text} from '../Typography'
-import {DateInput} from '#/view/com/util/forms/DateInput'
+import {cleanError} from '#/lib/strings/errors'
 import {logger} from '#/logger'
+import {isIOS, isWeb} from '#/platform/detection'
 import {
   usePreferencesQuery,
-  usePreferencesSetBirthDateMutation,
   UsePreferencesQueryResponse,
+  usePreferencesSetBirthDateMutation,
 } 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 {cleanError} from '#/lib/strings/errors'
-import {isIOS, isWeb} from '#/platform/detection'
+import {DateInput} from '#/view/com/util/forms/DateInput'
+import {atoms as a, useTheme} from '#/alf'
+import * as Dialog from '#/components/Dialog'
 import {Loader} from '#/components/Loader'
+import {Button, ButtonIcon, ButtonText} from '../Button'
+import {Text} from '../Typography'
 
 export function BirthDateSettingsDialog({
   control,
diff --git a/src/components/dialogs/SwitchAccount.tsx b/src/components/dialogs/SwitchAccount.tsx
new file mode 100644
index 000000000..645113d4a
--- /dev/null
+++ b/src/components/dialogs/SwitchAccount.tsx
@@ -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>
+  )
+}
diff --git a/src/screens/Login/ChooseAccountForm.tsx b/src/screens/Login/ChooseAccountForm.tsx
index d0d4c784d..15c06dbe8 100644
--- a/src/screens/Login/ChooseAccountForm.tsx
+++ b/src/screens/Login/ChooseAccountForm.tsx
@@ -5,76 +5,15 @@ import {useLingui} from '@lingui/react'
 
 import {useAnalytics} from '#/lib/analytics/analytics'
 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 {useLoggedOutViewControls} from '#/state/shell/logged-out'
 import * as Toast from '#/view/com/util/Toast'
-import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {atoms as a, useTheme} from '#/alf'
+import {atoms as a} from '#/alf'
+import {AccountList} from '#/components/AccountList'
 import {Button} from '#/components/Button'
 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'
 
-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 = ({
   onSelectAccount,
   onPressBack,
@@ -84,8 +23,7 @@ export const ChooseAccountForm = ({
 }) => {
   const {track, screen} = useAnalytics()
   const {_} = useLingui()
-  const t = useTheme()
-  const {accounts, currentAccount} = useSession()
+  const {currentAccount} = useSession()
   const {initSession} = useSessionApi()
   const {setShowLoggedOut} = useLoggedOutViewControls()
 
@@ -125,52 +63,10 @@ export const ChooseAccountForm = ({
         <TextField.Label>
           <Trans>Sign in as...</Trans>
         </TextField.Label>
-        <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={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>
+        <AccountList
+          onSelectAccount={onSelect}
+          onSelectOther={() => onSelectAccount()}
+        />
       </View>
       <View style={[a.flex_row]}>
         <Button
diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx
index 524dcb1ba..aae4fc52f 100644
--- a/src/state/modals/index.tsx
+++ b/src/state/modals/index.tsx
@@ -1,11 +1,11 @@
 import React from 'react'
-import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
 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 {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'
 
 export interface EditProfileModal {
@@ -118,10 +118,6 @@ export interface ChangePasswordModal {
   name: 'change-password'
 }
 
-export interface SwitchAccountModal {
-  name: 'switch-account'
-}
-
 export interface LinkWarningModal {
   name: 'link-warning'
   text: string
@@ -148,7 +144,6 @@ export type Modal =
   | VerifyEmailModal
   | ChangeEmailModal
   | ChangePasswordModal
-  | SwitchAccountModal
 
   // Curation
   | ContentLanguagesSettingsModal
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index af86f13a3..85ffccf12 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -24,7 +24,6 @@ import * as LinkWarningModal from './LinkWarning'
 import * as ListAddUserModal from './ListAddRemoveUsers'
 import * as RepostModal from './Repost'
 import * as SelfLabelModal from './SelfLabel'
-import * as SwitchAccountModal from './SwitchAccount'
 import * as ThreadgateModal from './Threadgate'
 import * as UserAddRemoveListsModal from './UserAddRemoveLists'
 import * as VerifyEmailModal from './VerifyEmail'
@@ -114,9 +113,6 @@ export function ModalsContainer() {
   } else if (activeModal?.name === 'change-password') {
     snapPoints = ChangePasswordModal.snapPoints
     element = <ChangePasswordModal.Component />
-  } else if (activeModal?.name === 'switch-account') {
-    snapPoints = SwitchAccountModal.snapPoints
-    element = <SwitchAccountModal.Component />
   } else if (activeModal?.name === 'link-warning') {
     snapPoints = LinkWarningModal.snapPoints
     element = <LinkWarningModal.Component {...activeModal} />
diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx
deleted file mode 100644
index 03bef719e..000000000
--- a/src/view/com/modals/SwitchAccount.tsx
+++ /dev/null
@@ -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,
-  },
-})
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index 8a19a0b4f..f41631a96 100644
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -1,47 +1,49 @@
 import React, {ComponentProps} from 'react'
 import {GestureResponderEvent, TouchableOpacity, View} from 'react-native'
 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 {Text} from 'view/com/util/text/Text'
-import {useAnalytics} from 'lib/analytics/analytics'
-import {clamp} from 'lib/numbers'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+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 {
+  BellIcon,
+  BellIconSolid,
+  HashtagIcon,
   HomeIcon,
   HomeIconSolid,
   MagnifyingGlassIcon2,
   MagnifyingGlassIcon2Solid,
-  HashtagIcon,
-  BellIcon,
-  BellIconSolid,
-} from 'lib/icons'
-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'
+} from '#/lib/icons'
+import {clamp} from '#/lib/numbers'
+import {getTabState, TabState} from '#/lib/routes/helpers'
+import {s} from '#/lib/styles'
 import {emitSoftReset} from '#/state/events'
-import {useSession} from '#/state/session'
+import {useUnreadNotifications} from '#/state/queries/notifications/unread'
 import {useProfileQuery} from '#/state/queries/profile'
+import {useSession} from '#/state/session'
 import {useLoggedOutViewControls} from '#/state/shell/logged-out'
+import {useShellLayout} from '#/state/shell/shell-layout'
 import {useCloseAllActiveElements} from '#/state/util'
 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 {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'
 
 export function BottomBar({navigation}: BottomTabBarProps) {
-  const {openModal} = useModalControls()
   const {hasSession, currentAccount} = useSession()
   const pal = usePalette('default')
   const {_} = useLingui()
@@ -56,6 +58,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
   const {requestSwitchToAccount} = useLoggedOutViewControls()
   const closeAllActiveElements = useCloseAllActiveElements()
   const dedupe = useDedupe()
+  const accountSwitchControl = useDialogControl()
 
   const showSignIn = React.useCallback(() => {
     closeAllActiveElements()
@@ -99,204 +102,213 @@ export function BottomBar({navigation}: BottomTabBarProps) {
   const onPressProfile = React.useCallback(() => {
     onPressTab('MyProfile')
   }, [onPressTab])
+
   const onLongPressProfile = React.useCallback(() => {
-    openModal({name: 'switch-account'})
-  }, [openModal])
+    Haptics.default()
+    accountSwitchControl.open()
+  }, [accountSwitchControl])
 
   return (
-    <Animated.View
-      style={[
-        styles.bottomBar,
-        pal.view,
-        pal.border,
-        {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
-        footerMinimalShellTransform,
-      ]}
-      onLayout={e => {
-        footerHeight.value = e.nativeEvent.layout.height
-      }}>
-      {hasSession ? (
-        <>
-          <Btn
-            testID="bottomBarHomeBtn"
-            icon={
-              isAtHome ? (
-                <HomeIconSolid
-                  strokeWidth={4}
-                  size={24}
-                  style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
-                />
-              ) : (
-                <HomeIcon
-                  strokeWidth={4}
-                  size={24}
-                  style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
-                />
-              )
-            }
-            onPress={onPressHome}
-            accessibilityRole="tab"
-            accessibilityLabel={_(msg`Home`)}
-            accessibilityHint=""
-          />
-          <Btn
-            testID="bottomBarSearchBtn"
-            icon={
-              isAtSearch ? (
-                <MagnifyingGlassIcon2Solid
-                  size={25}
-                  style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
-                  strokeWidth={1.8}
-                />
-              ) : (
-                <MagnifyingGlassIcon2
-                  size={25}
-                  style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
-                  strokeWidth={1.8}
-                />
-              )
-            }
-            onPress={onPressSearch}
-            accessibilityRole="search"
-            accessibilityLabel={_(msg`Search`)}
-            accessibilityHint=""
-          />
-          <Btn
-            testID="bottomBarFeedsBtn"
-            icon={
-              isAtFeeds ? (
-                <HashtagIcon
-                  size={24}
-                  style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
-                  strokeWidth={4}
-                />
-              ) : (
-                <HashtagIcon
-                  size={24}
-                  style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
-                  strokeWidth={2.25}
-                />
-              )
-            }
-            onPress={onPressFeeds}
-            accessibilityRole="tab"
-            accessibilityLabel={_(msg`Feeds`)}
-            accessibilityHint=""
-          />
-          <Btn
-            testID="bottomBarNotificationsBtn"
-            icon={
-              isAtNotifications ? (
-                <BellIconSolid
-                  size={24}
-                  strokeWidth={1.9}
-                  style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
-                />
-              ) : (
-                <BellIcon
-                  size={24}
-                  strokeWidth={1.9}
-                  style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
-                />
-              )
-            }
-            onPress={onPressNotifications}
-            notificationCount={numUnreadNotifications}
-            accessible={true}
-            accessibilityRole="tab"
-            accessibilityLabel={_(msg`Notifications`)}
-            accessibilityHint={
-              numUnreadNotifications === ''
-                ? ''
-                : `${numUnreadNotifications} unread`
-            }
-          />
-          <Btn
-            testID="bottomBarProfileBtn"
-            icon={
-              <View style={styles.ctrlIconSizingWrapper}>
-                {isAtMyProfile ? (
-                  <View
-                    style={[
-                      styles.ctrlIcon,
-                      pal.text,
-                      styles.profileIcon,
-                      styles.onProfile,
-                      {borderColor: pal.text.color},
-                    ]}>
-                    <UserAvatar
-                      avatar={profile?.avatar}
-                      size={27}
-                      // See https://github.com/bluesky-social/social-app/pull/1801:
-                      usePlainRNImage={true}
-                      type={profile?.associated?.labeler ? 'labeler' : 'user'}
-                    />
-                  </View>
+    <>
+      <SwitchAccountDialog control={accountSwitchControl} />
+
+      <Animated.View
+        style={[
+          styles.bottomBar,
+          pal.view,
+          pal.border,
+          {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
+          footerMinimalShellTransform,
+        ]}
+        onLayout={e => {
+          footerHeight.value = e.nativeEvent.layout.height
+        }}>
+        {hasSession ? (
+          <>
+            <Btn
+              testID="bottomBarHomeBtn"
+              icon={
+                isAtHome ? (
+                  <HomeIconSolid
+                    strokeWidth={4}
+                    size={24}
+                    style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
+                  />
                 ) : (
-                  <View style={[styles.ctrlIcon, pal.text, styles.profileIcon]}>
-                    <UserAvatar
-                      avatar={profile?.avatar}
-                      size={28}
-                      // See https://github.com/bluesky-social/social-app/pull/1801:
-                      usePlainRNImage={true}
-                      type={profile?.associated?.labeler ? 'labeler' : 'user'}
-                    />
-                  </View>
-                )}
-              </View>
-            }
-            onPress={onPressProfile}
-            onLongPress={onLongPressProfile}
-            accessibilityRole="tab"
-            accessibilityLabel={_(msg`Profile`)}
-            accessibilityHint=""
-          />
-        </>
-      ) : (
-        <>
-          <View
-            style={{
-              width: '100%',
-              flexDirection: 'row',
-              alignItems: 'center',
-              justifyContent: 'space-between',
-              paddingTop: 14,
-              paddingBottom: 2,
-              paddingLeft: 14,
-              paddingRight: 6,
-              gap: 8,
-            }}>
-            <View style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
-              <Logo width={28} />
-              <View style={{paddingTop: 4}}>
-                <Logotype width={80} fill={pal.text.color} />
+                  <HomeIcon
+                    strokeWidth={4}
+                    size={24}
+                    style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
+                  />
+                )
+              }
+              onPress={onPressHome}
+              accessibilityRole="tab"
+              accessibilityLabel={_(msg`Home`)}
+              accessibilityHint=""
+            />
+            <Btn
+              testID="bottomBarSearchBtn"
+              icon={
+                isAtSearch ? (
+                  <MagnifyingGlassIcon2Solid
+                    size={25}
+                    style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
+                    strokeWidth={1.8}
+                  />
+                ) : (
+                  <MagnifyingGlassIcon2
+                    size={25}
+                    style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
+                    strokeWidth={1.8}
+                  />
+                )
+              }
+              onPress={onPressSearch}
+              accessibilityRole="search"
+              accessibilityLabel={_(msg`Search`)}
+              accessibilityHint=""
+            />
+            <Btn
+              testID="bottomBarFeedsBtn"
+              icon={
+                isAtFeeds ? (
+                  <HashtagIcon
+                    size={24}
+                    style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
+                    strokeWidth={4}
+                  />
+                ) : (
+                  <HashtagIcon
+                    size={24}
+                    style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
+                    strokeWidth={2.25}
+                  />
+                )
+              }
+              onPress={onPressFeeds}
+              accessibilityRole="tab"
+              accessibilityLabel={_(msg`Feeds`)}
+              accessibilityHint=""
+            />
+            <Btn
+              testID="bottomBarNotificationsBtn"
+              icon={
+                isAtNotifications ? (
+                  <BellIconSolid
+                    size={24}
+                    strokeWidth={1.9}
+                    style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
+                  />
+                ) : (
+                  <BellIcon
+                    size={24}
+                    strokeWidth={1.9}
+                    style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
+                  />
+                )
+              }
+              onPress={onPressNotifications}
+              notificationCount={numUnreadNotifications}
+              accessible={true}
+              accessibilityRole="tab"
+              accessibilityLabel={_(msg`Notifications`)}
+              accessibilityHint={
+                numUnreadNotifications === ''
+                  ? ''
+                  : `${numUnreadNotifications} unread`
+              }
+            />
+            <Btn
+              testID="bottomBarProfileBtn"
+              icon={
+                <View style={styles.ctrlIconSizingWrapper}>
+                  {isAtMyProfile ? (
+                    <View
+                      style={[
+                        styles.ctrlIcon,
+                        pal.text,
+                        styles.profileIcon,
+                        styles.onProfile,
+                        {borderColor: pal.text.color},
+                      ]}>
+                      <UserAvatar
+                        avatar={profile?.avatar}
+                        size={27}
+                        // See https://github.com/bluesky-social/social-app/pull/1801:
+                        usePlainRNImage={true}
+                        type={profile?.associated?.labeler ? 'labeler' : 'user'}
+                      />
+                    </View>
+                  ) : (
+                    <View
+                      style={[styles.ctrlIcon, pal.text, styles.profileIcon]}>
+                      <UserAvatar
+                        avatar={profile?.avatar}
+                        size={28}
+                        // See https://github.com/bluesky-social/social-app/pull/1801:
+                        usePlainRNImage={true}
+                        type={profile?.associated?.labeler ? 'labeler' : 'user'}
+                      />
+                    </View>
+                  )}
+                </View>
+              }
+              onPress={onPressProfile}
+              onLongPress={onLongPressProfile}
+              accessibilityRole="tab"
+              accessibilityLabel={_(msg`Profile`)}
+              accessibilityHint=""
+            />
+          </>
+        ) : (
+          <>
+            <View
+              style={{
+                width: '100%',
+                flexDirection: 'row',
+                alignItems: 'center',
+                justifyContent: 'space-between',
+                paddingTop: 14,
+                paddingBottom: 2,
+                paddingLeft: 14,
+                paddingRight: 6,
+                gap: 8,
+              }}>
+              <View
+                style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
+                <Logo width={28} />
+                <View style={{paddingTop: 4}}>
+                  <Logotype width={80} fill={pal.text.color} />
+                </View>
               </View>
-            </View>
 
-            <View style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
-              <Button
-                onPress={showCreateAccount}
-                accessibilityHint={_(msg`Sign up`)}
-                accessibilityLabel={_(msg`Sign up`)}>
-                <Text type="md" style={[{color: 'white'}, s.bold]}>
-                  <Trans>Sign up</Trans>
-                </Text>
-              </Button>
+              <View
+                style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
+                <Button
+                  onPress={showCreateAccount}
+                  accessibilityHint={_(msg`Sign up`)}
+                  accessibilityLabel={_(msg`Sign up`)}>
+                  <Text type="md" style={[{color: 'white'}, s.bold]}>
+                    <Trans>Sign up</Trans>
+                  </Text>
+                </Button>
 
-              <Button
-                type="default"
-                onPress={showSignIn}
-                accessibilityHint={_(msg`Sign in`)}
-                accessibilityLabel={_(msg`Sign in`)}>
-                <Text type="md" style={[pal.text, s.bold]}>
-                  <Trans>Sign in</Trans>
-                </Text>
-              </Button>
+                <Button
+                  type="default"
+                  onPress={showSignIn}
+                  accessibilityHint={_(msg`Sign in`)}
+                  accessibilityLabel={_(msg`Sign in`)}>
+                  <Text type="md" style={[pal.text, s.bold]}>
+                    <Trans>Sign in</Trans>
+                  </Text>
+                </Button>
+              </View>
             </View>
-          </View>
-        </>
-      )}
-    </Animated.View>
+          </>
+        )}
+      </Animated.View>
+    </>
   )
 }