about summary refs log tree commit diff
path: root/src/view/shell/Drawer.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/shell/Drawer.tsx')
-rw-r--r--src/view/shell/Drawer.tsx367
1 files changed, 209 insertions, 158 deletions
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
index 7f5e6c5e5..459a021c4 100644
--- a/src/view/shell/Drawer.tsx
+++ b/src/view/shell/Drawer.tsx
@@ -10,14 +10,13 @@ import {
   ViewStyle,
 } from 'react-native'
 import {useNavigation, StackActions} from '@react-navigation/native'
-import {observer} from 'mobx-react-lite'
 import {
   FontAwesomeIcon,
   FontAwesomeIconStyle,
 } from '@fortawesome/react-native-fontawesome'
+import {useQueryClient} from '@tanstack/react-query'
 import {s, colors} from 'lib/styles'
 import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
-import {useStores} from 'state/index'
 import {
   HomeIcon,
   HomeIconSolid,
@@ -42,20 +41,81 @@ import {getTabState, TabState} from 'lib/routes/helpers'
 import {NavigationProp} from 'lib/routes/types'
 import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
 import {isWeb} from 'platform/detection'
-import {formatCount, formatCountShortOnly} from 'view/com/util/numeric/format'
+import {formatCountShortOnly} from 'view/com/util/numeric/format'
+import {Trans, msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 import {useSetDrawerOpen} from '#/state/shell'
+import {useModalControls} from '#/state/modals'
+import {useSession, SessionAccount} from '#/state/session'
+import {useProfileQuery} from '#/state/queries/profile'
+import {useUnreadNotifications} from '#/state/queries/notifications/unread'
+import {emitSoftReset} from '#/state/events'
+import {useInviteCodesQuery} from '#/state/queries/invites'
+import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
+import {NavSignupCard} from '#/view/shell/NavSignupCard'
+import {truncateAndInvalidate} from '#/state/queries/util'
 
-export const DrawerContent = observer(function DrawerContentImpl() {
+export function DrawerProfileCard({
+  account,
+  onPressProfile,
+}: {
+  account: SessionAccount
+  onPressProfile: () => void
+}) {
+  const {_} = useLingui()
+  const pal = usePalette('default')
+  const {data: profile} = useProfileQuery({did: account.did})
+
+  return (
+    <TouchableOpacity
+      testID="profileCardButton"
+      accessibilityLabel={_(msg`Profile`)}
+      accessibilityHint="Navigates to your profile"
+      onPress={onPressProfile}>
+      <UserAvatar
+        size={80}
+        avatar={profile?.avatar}
+        // See https://github.com/bluesky-social/social-app/pull/1801:
+        usePlainRNImage={true}
+      />
+      <Text
+        type="title-lg"
+        style={[pal.text, s.bold, styles.profileCardDisplayName]}
+        numberOfLines={1}>
+        {profile?.displayName || account.handle}
+      </Text>
+      <Text
+        type="2xl"
+        style={[pal.textLight, styles.profileCardHandle]}
+        numberOfLines={1}>
+        @{account.handle}
+      </Text>
+      <Text type="xl" style={[pal.textLight, styles.profileCardFollowers]}>
+        <Text type="xl-medium" style={pal.text}>
+          {formatCountShortOnly(profile?.followersCount ?? 0)}
+        </Text>{' '}
+        {pluralize(profile?.followersCount || 0, 'follower')} &middot;{' '}
+        <Text type="xl-medium" style={pal.text}>
+          {formatCountShortOnly(profile?.followsCount ?? 0)}
+        </Text>{' '}
+        following
+      </Text>
+    </TouchableOpacity>
+  )
+}
+
+export function DrawerContent() {
   const theme = useTheme()
   const pal = usePalette('default')
-  const store = useStores()
+  const {_} = useLingui()
+  const queryClient = useQueryClient()
   const setDrawerOpen = useSetDrawerOpen()
   const navigation = useNavigation<NavigationProp>()
   const {track} = useAnalytics()
   const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
     useNavigationTabState()
-
-  const {notifications} = store.me
+  const {hasSession, currentAccount} = useSession()
+  const numUnreadNotifications = useUnreadNotifications()
 
   // events
   // =
@@ -68,7 +128,7 @@ export const DrawerContent = observer(function DrawerContentImpl() {
       if (isWeb) {
         // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
         if (tab === 'MyProfile') {
-          navigation.navigate('Profile', {name: store.me.handle})
+          navigation.navigate('Profile', {name: currentAccount!.handle})
         } else {
           // @ts-ignore must be Home, Search, Notifications, or MyProfile
           navigation.navigate(tab)
@@ -76,16 +136,20 @@ export const DrawerContent = observer(function DrawerContentImpl() {
       } else {
         const tabState = getTabState(state, tab)
         if (tabState === TabState.InsideAtRoot) {
-          store.emitScreenSoftReset()
+          emitSoftReset()
         } else if (tabState === TabState.Inside) {
           navigation.dispatch(StackActions.popToTop())
         } else {
+          if (tab === 'Notifications') {
+            // fetch new notifs on view
+            truncateAndInvalidate(queryClient, NOTIFS_RQKEY())
+          }
           // @ts-ignore must be Home, Search, Notifications, or MyProfile
           navigation.navigate(`${tab}Tab`)
         }
       }
     },
-    [store, track, navigation, setDrawerOpen],
+    [track, navigation, setDrawerOpen, currentAccount, queryClient],
   )
 
   const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
@@ -131,11 +195,11 @@ export const DrawerContent = observer(function DrawerContentImpl() {
     track('Menu:FeedbackClicked')
     Linking.openURL(
       FEEDBACK_FORM_URL({
-        email: store.session.currentSession?.email,
-        handle: store.session.currentSession?.handle,
+        email: currentAccount?.email,
+        handle: currentAccount?.handle,
       }),
     )
-  }, [track, store.session.currentSession])
+  }, [track, currentAccount])
 
   const onPressHelp = React.useCallback(() => {
     track('Menu:HelpClicked')
@@ -154,48 +218,20 @@ export const DrawerContent = observer(function DrawerContentImpl() {
       ]}>
       <SafeAreaView style={s.flex1}>
         <ScrollView style={styles.main}>
-          <View style={{}}>
-            <TouchableOpacity
-              testID="profileCardButton"
-              accessibilityLabel="Profile"
-              accessibilityHint="Navigates to your profile"
-              onPress={onPressProfile}>
-              <UserAvatar
-                size={80}
-                avatar={store.me.avatar}
-                // See https://github.com/bluesky-social/social-app/pull/1801:
-                usePlainRNImage={true}
+          {hasSession && currentAccount ? (
+            <View style={{}}>
+              <DrawerProfileCard
+                account={currentAccount}
+                onPressProfile={onPressProfile}
               />
-              <Text
-                type="title-lg"
-                style={[pal.text, s.bold, styles.profileCardDisplayName]}
-                numberOfLines={1}>
-                {store.me.displayName || store.me.handle}
-              </Text>
-              <Text
-                type="2xl"
-                style={[pal.textLight, styles.profileCardHandle]}
-                numberOfLines={1}>
-                @{store.me.handle}
-              </Text>
-              <Text
-                type="xl"
-                style={[pal.textLight, styles.profileCardFollowers]}>
-                <Text type="xl-medium" style={pal.text}>
-                  {formatCountShortOnly(store.me.followersCount ?? 0)}
-                </Text>{' '}
-                {pluralize(store.me.followersCount || 0, 'follower')} &middot;{' '}
-                <Text type="xl-medium" style={pal.text}>
-                  {formatCountShortOnly(store.me.followsCount ?? 0)}
-                </Text>{' '}
-                following
-              </Text>
-            </TouchableOpacity>
-          </View>
+            </View>
+          ) : (
+            <NavSignupCard />
+          )}
 
-          <InviteCodes style={{paddingLeft: 0}} />
+          {hasSession && <InviteCodes style={{paddingLeft: 0}} />}
 
-          <View style={{height: 10}} />
+          {hasSession && <View style={{height: 10}} />}
 
           <MenuItem
             icon={
@@ -213,8 +249,8 @@ export const DrawerContent = observer(function DrawerContentImpl() {
                 />
               )
             }
-            label="Search"
-            accessibilityLabel="Search"
+            label={_(msg`Search`)}
+            accessibilityLabel={_(msg`Search`)}
             accessibilityHint=""
             bold={isAtSearch}
             onPress={onPressSearch}
@@ -235,39 +271,43 @@ export const DrawerContent = observer(function DrawerContentImpl() {
                 />
               )
             }
-            label="Home"
-            accessibilityLabel="Home"
+            label={_(msg`Home`)}
+            accessibilityLabel={_(msg`Home`)}
             accessibilityHint=""
             bold={isAtHome}
             onPress={onPressHome}
           />
-          <MenuItem
-            icon={
-              isAtNotifications ? (
-                <BellIconSolid
-                  style={pal.text as StyleProp<ViewStyle>}
-                  size="24"
-                  strokeWidth={1.7}
-                />
-              ) : (
-                <BellIcon
-                  style={pal.text as StyleProp<ViewStyle>}
-                  size="24"
-                  strokeWidth={1.7}
-                />
-              )
-            }
-            label="Notifications"
-            accessibilityLabel="Notifications"
-            accessibilityHint={
-              notifications.unreadCountLabel === ''
-                ? ''
-                : `${notifications.unreadCountLabel} unread`
-            }
-            count={notifications.unreadCountLabel}
-            bold={isAtNotifications}
-            onPress={onPressNotifications}
-          />
+
+          {hasSession && (
+            <MenuItem
+              icon={
+                isAtNotifications ? (
+                  <BellIconSolid
+                    style={pal.text as StyleProp<ViewStyle>}
+                    size="24"
+                    strokeWidth={1.7}
+                  />
+                ) : (
+                  <BellIcon
+                    style={pal.text as StyleProp<ViewStyle>}
+                    size="24"
+                    strokeWidth={1.7}
+                  />
+                )
+              }
+              label={_(msg`Notifications`)}
+              accessibilityLabel={_(msg`Notifications`)}
+              accessibilityHint={
+                numUnreadNotifications === ''
+                  ? ''
+                  : `${numUnreadNotifications} unread`
+              }
+              count={numUnreadNotifications}
+              bold={isAtNotifications}
+              onPress={onPressNotifications}
+            />
+          )}
+
           <MenuItem
             icon={
               isAtFeeds ? (
@@ -284,68 +324,74 @@ export const DrawerContent = observer(function DrawerContentImpl() {
                 />
               )
             }
-            label="Feeds"
-            accessibilityLabel="Feeds"
+            label={_(msg`Feeds`)}
+            accessibilityLabel={_(msg`Feeds`)}
             accessibilityHint=""
             bold={isAtFeeds}
             onPress={onPressMyFeeds}
           />
-          <MenuItem
-            icon={<ListIcon strokeWidth={2} style={pal.text} size={26} />}
-            label="Lists"
-            accessibilityLabel="Lists"
-            accessibilityHint=""
-            onPress={onPressLists}
-          />
-          <MenuItem
-            icon={<HandIcon strokeWidth={5} style={pal.text} size={24} />}
-            label="Moderation"
-            accessibilityLabel="Moderation"
-            accessibilityHint=""
-            onPress={onPressModeration}
-          />
-          <MenuItem
-            icon={
-              isAtMyProfile ? (
-                <UserIconSolid
-                  style={pal.text as StyleProp<ViewStyle>}
-                  size="26"
-                  strokeWidth={1.5}
-                />
-              ) : (
-                <UserIcon
-                  style={pal.text as StyleProp<ViewStyle>}
-                  size="26"
-                  strokeWidth={1.5}
-                />
-              )
-            }
-            label="Profile"
-            accessibilityLabel="Profile"
-            accessibilityHint=""
-            onPress={onPressProfile}
-          />
-          <MenuItem
-            icon={
-              <CogIcon
-                style={pal.text as StyleProp<ViewStyle>}
-                size="26"
-                strokeWidth={1.75}
+
+          {hasSession && (
+            <>
+              <MenuItem
+                icon={<ListIcon strokeWidth={2} style={pal.text} size={26} />}
+                label={_(msg`Lists`)}
+                accessibilityLabel={_(msg`Lists`)}
+                accessibilityHint=""
+                onPress={onPressLists}
               />
-            }
-            label="Settings"
-            accessibilityLabel="Settings"
-            accessibilityHint=""
-            onPress={onPressSettings}
-          />
+              <MenuItem
+                icon={<HandIcon strokeWidth={5} style={pal.text} size={24} />}
+                label={_(msg`Moderation`)}
+                accessibilityLabel={_(msg`Moderation`)}
+                accessibilityHint=""
+                onPress={onPressModeration}
+              />
+              <MenuItem
+                icon={
+                  isAtMyProfile ? (
+                    <UserIconSolid
+                      style={pal.text as StyleProp<ViewStyle>}
+                      size="26"
+                      strokeWidth={1.5}
+                    />
+                  ) : (
+                    <UserIcon
+                      style={pal.text as StyleProp<ViewStyle>}
+                      size="26"
+                      strokeWidth={1.5}
+                    />
+                  )
+                }
+                label={_(msg`Profile`)}
+                accessibilityLabel={_(msg`Profile`)}
+                accessibilityHint=""
+                onPress={onPressProfile}
+              />
+              <MenuItem
+                icon={
+                  <CogIcon
+                    style={pal.text as StyleProp<ViewStyle>}
+                    size="26"
+                    strokeWidth={1.75}
+                  />
+                }
+                label={_(msg`Settings`)}
+                accessibilityLabel={_(msg`Settings`)}
+                accessibilityHint=""
+                onPress={onPressSettings}
+              />
+            </>
+          )}
 
           <View style={styles.smallSpacer} />
           <View style={styles.smallSpacer} />
         </ScrollView>
+
         <View style={styles.footer}>
           <TouchableOpacity
             accessibilityRole="link"
-            accessibilityLabel="Send feedback"
+            accessibilityLabel={_(msg`Send feedback`)}
             accessibilityHint=""
             onPress={onPressFeedback}
             style={[
@@ -361,24 +407,24 @@ export const DrawerContent = observer(function DrawerContentImpl() {
               icon={['far', 'message']}
             />
             <Text type="lg-medium" style={[pal.link, s.pl10]}>
-              Feedback
+              <Trans>Feedback</Trans>
             </Text>
           </TouchableOpacity>
           <TouchableOpacity
             accessibilityRole="link"
-            accessibilityLabel="Send feedback"
+            accessibilityLabel={_(msg`Send feedback`)}
             accessibilityHint=""
             onPress={onPressHelp}
             style={[styles.footerBtn]}>
             <Text type="lg-medium" style={[pal.link, s.pl10]}>
-              Help
+              <Trans>Help</Trans>
             </Text>
           </TouchableOpacity>
         </View>
       </SafeAreaView>
     </View>
   )
-})
+}
 
 interface MenuItemProps extends ComponentProps<typeof TouchableOpacity> {
   icon: JSX.Element
@@ -432,50 +478,54 @@ function MenuItem({
   )
 }
 
-const InviteCodes = observer(function InviteCodesImpl({
-  style,
-}: {
-  style?: StyleProp<ViewStyle>
-}) {
+function InviteCodes({style}: {style?: StyleProp<ViewStyle>}) {
   const {track} = useAnalytics()
-  const store = useStores()
   const setDrawerOpen = useSetDrawerOpen()
   const pal = usePalette('default')
-  const {invitesAvailable} = store.me
+  const {data: invites} = useInviteCodesQuery()
+  const invitesAvailable = invites?.available?.length ?? 0
+  const {openModal} = useModalControls()
+  const {_} = useLingui()
+
   const onPress = React.useCallback(() => {
     track('Menu:ItemClicked', {url: '#invite-codes'})
     setDrawerOpen(false)
-    store.shell.openModal({name: 'invite-codes'})
-  }, [store, track, setDrawerOpen])
+    openModal({name: 'invite-codes'})
+  }, [openModal, track, setDrawerOpen])
+
   return (
     <TouchableOpacity
       testID="menuItemInviteCodes"
       style={[styles.inviteCodes, style]}
       onPress={onPress}
       accessibilityRole="button"
-      accessibilityLabel={
-        invitesAvailable === 1
-          ? 'Invite codes: 1 available'
-          : `Invite codes: ${invitesAvailable} available`
-      }
-      accessibilityHint="Opens list of invite codes">
+      accessibilityLabel={_(msg`Invite codes: ${invitesAvailable} available`)}
+      accessibilityHint={_(msg`Opens list of invite codes`)}
+      disabled={invites?.disabled}>
       <FontAwesomeIcon
         icon="ticket"
         style={[
           styles.inviteCodesIcon,
-          store.me.invitesAvailable > 0 ? pal.link : pal.textLight,
+          invitesAvailable > 0 ? pal.link : pal.textLight,
         ]}
         size={18}
       />
       <Text
         type="lg-medium"
-        style={store.me.invitesAvailable > 0 ? pal.link : pal.textLight}>
-        {formatCount(store.me.invitesAvailable)} invite{' '}
-        {pluralize(store.me.invitesAvailable, 'code')}
+        style={invitesAvailable > 0 ? pal.link : pal.textLight}>
+        {invites?.disabled ? (
+          <Trans>
+            Your invite codes are hidden when logged in using an App Password
+          </Trans>
+        ) : invitesAvailable === 1 ? (
+          <Trans>{invitesAvailable} invite code available</Trans>
+        ) : (
+          <Trans>{invitesAvailable} invite codes available</Trans>
+        )}
       </Text>
     </TouchableOpacity>
   )
-})
+}
 
 const styles = StyleSheet.create({
   view: {
@@ -548,10 +598,11 @@ const styles = StyleSheet.create({
     paddingLeft: 22,
     paddingVertical: 8,
     flexDirection: 'row',
-    alignItems: 'center',
   },
   inviteCodesIcon: {
     marginRight: 6,
+    flexShrink: 0,
+    marginTop: 2,
   },
 
   footer: {