about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorAnsh <anshnanda10@gmail.com>2023-04-18 09:19:37 -0700
committerGitHub <noreply@github.com>2023-04-18 11:19:37 -0500
commit10621e86e4379ff05b2262a659b8512d80203a4b (patch)
treefdc91b7db00526f945d9463b732785da6cceb5c7 /src
parent2509290fdd2b20c76c302d4962216f5d2d2b5a73 (diff)
downloadvoidsky-10621e86e4379ff05b2262a659b8512d80203a4b.tar.zst
APP-70 give profile its own tab mobile (#469)
* add prebuild command to package.json

* add ProfileTab navigator and screen

* add prop to remove back button from profile

* fix MyProfileTabNavigatorParams type

* fix dep array for rendering ProfileHeader

* just added ts-ignore

* enable opening drawer in profile tab

* clean up useNavigationTabState

* clean up code

* fix hideBackButton code flow
Diffstat (limited to 'src')
-rw-r--r--src/Navigation.tsx33
-rw-r--r--src/lib/hooks/useNavigationTabState.ts15
-rw-r--r--src/lib/hooks/useNavigationTabState.web.ts1
-rw-r--r--src/lib/routes/helpers.ts3
-rw-r--r--src/lib/routes/types.ts14
-rw-r--r--src/view/com/profile/ProfileHeader.tsx24
-rw-r--r--src/view/screens/Profile.tsx10
-rw-r--r--src/view/shell/Drawer.tsx32
-rw-r--r--src/view/shell/bottom-bar/BottomBar.tsx46
9 files changed, 127 insertions, 51 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index 0de31a4ad..e868dd3b0 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -13,6 +13,8 @@ import {
   NotificationsTabNavigatorParams,
   FlatNavigatorParams,
   AllNavigatorParams,
+  MyProfileTabNavigatorParams,
+  BottomTabNavigatorParams,
 } from 'lib/routes/types'
 import {BottomBar} from './view/shell/bottom-bar/BottomBar'
 import {buildStateObject} from 'lib/routes/helpers'
@@ -41,6 +43,7 @@ import {TermsOfServiceScreen} from './view/screens/TermsOfService'
 import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines'
 import {CopyrightPolicyScreen} from './view/screens/CopyrightPolicy'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useStores} from './state'
 
 const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
 
@@ -48,8 +51,9 @@ const HomeTab = createNativeStackNavigator<HomeTabNavigatorParams>()
 const SearchTab = createNativeStackNavigator<SearchTabNavigatorParams>()
 const NotificationsTab =
   createNativeStackNavigator<NotificationsTabNavigatorParams>()
+const MyProfileTab = createNativeStackNavigator<MyProfileTabNavigatorParams>()
 const Flat = createNativeStackNavigator<FlatNavigatorParams>()
-const Tab = createBottomTabNavigator()
+const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
 
 /**
  * These "common screens" are reused across stacks.
@@ -100,6 +104,7 @@ function TabsNavigator() {
         component={NotificationsTabNavigator}
       />
       <Tab.Screen name="SearchTab" component={SearchTabNavigator} />
+      <Tab.Screen name="MyProfileTab" component={MyProfileTabNavigator} />
     </Tab.Navigator>
   )
 }
@@ -158,6 +163,32 @@ function NotificationsTabNavigator() {
   )
 }
 
+function MyProfileTabNavigator() {
+  const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark)
+  const store = useStores()
+  return (
+    <MyProfileTab.Navigator
+      screenOptions={{
+        gestureEnabled: true,
+        fullScreenGestureEnabled: true,
+        headerShown: false,
+        animationDuration: 250,
+        contentStyle,
+      }}>
+      <MyProfileTab.Screen
+        name="MyProfile"
+        // @ts-ignore // TODO: fix this broken type in ProfileScreen
+        component={ProfileScreen}
+        initialParams={{
+          name: store.me.handle,
+          hideBackButton: true,
+        }}
+      />
+      {commonScreens(MyProfileTab as typeof HomeTab)}
+    </MyProfileTab.Navigator>
+  )
+}
+
 /**
  * The FlatNavigator is used by Web to represent the routes
  * in a single ("flat") stack.
diff --git a/src/lib/hooks/useNavigationTabState.ts b/src/lib/hooks/useNavigationTabState.ts
index 8afc799eb..fb3662152 100644
--- a/src/lib/hooks/useNavigationTabState.ts
+++ b/src/lib/hooks/useNavigationTabState.ts
@@ -3,11 +3,24 @@ import {getTabState, TabState} from 'lib/routes/helpers'
 
 export function useNavigationTabState() {
   return useNavigationState(state => {
-    return {
+    const res = {
       isAtHome: getTabState(state, 'Home') !== TabState.Outside,
       isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
       isAtNotifications:
         getTabState(state, 'Notifications') !== TabState.Outside,
+      isAtMyProfile: getTabState(state, 'MyProfile') !== TabState.Outside,
     }
+    if (
+      !res.isAtHome &&
+      !res.isAtNotifications &&
+      !res.isAtSearch &&
+      !res.isAtMyProfile
+    ) {
+      // HACK for some reason useNavigationState will give us pre-hydration results
+      //      and not update after, so we force isAtHome if all came back false
+      //      -prf
+      res.isAtHome = true
+    }
+    return res
   })
 }
diff --git a/src/lib/hooks/useNavigationTabState.web.ts b/src/lib/hooks/useNavigationTabState.web.ts
index d0173aa0f..704424781 100644
--- a/src/lib/hooks/useNavigationTabState.web.ts
+++ b/src/lib/hooks/useNavigationTabState.web.ts
@@ -8,6 +8,7 @@ export function useNavigationTabState() {
       isAtHome: currentRoute === 'Home',
       isAtSearch: currentRoute === 'Search',
       isAtNotifications: currentRoute === 'Notifications',
+      isAtMyProfile: currentRoute === 'MyProfile',
     }
   })
 }
diff --git a/src/lib/routes/helpers.ts b/src/lib/routes/helpers.ts
index be76b9669..cfa6ae53b 100644
--- a/src/lib/routes/helpers.ts
+++ b/src/lib/routes/helpers.ts
@@ -20,7 +20,8 @@ export function isStateAtTabRoot(state: State | undefined) {
   return (
     isTab(currentRoute.name, 'Home') ||
     isTab(currentRoute.name, 'Search') ||
-    isTab(currentRoute.name, 'Notifications')
+    isTab(currentRoute.name, 'Notifications') ||
+    isTab(currentRoute.name, 'MyProfile')
   )
 }
 
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index 48edcc956..f8698f1cc 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -6,7 +6,7 @@ export type {NativeStackScreenProps} from '@react-navigation/native-stack'
 export type CommonNavigatorParams = {
   NotFound: undefined
   Settings: undefined
-  Profile: {name: string}
+  Profile: {name: string; hideBackButton?: boolean}
   ProfileFollowers: {name: string}
   ProfileFollows: {name: string}
   PostThread: {name: string; rkey: string}
@@ -21,6 +21,13 @@ export type CommonNavigatorParams = {
   CopyrightPolicy: undefined
 }
 
+export type BottomTabNavigatorParams = CommonNavigatorParams & {
+  HomeTab: undefined
+  SearchTab: undefined
+  NotificationsTab: undefined
+  MyProfileTab: undefined
+}
+
 export type HomeTabNavigatorParams = CommonNavigatorParams & {
   Home: undefined
 }
@@ -33,6 +40,10 @@ export type NotificationsTabNavigatorParams = CommonNavigatorParams & {
   Notifications: undefined
 }
 
+export type MyProfileTabNavigatorParams = CommonNavigatorParams & {
+  MyProfile: undefined
+}
+
 export type FlatNavigatorParams = CommonNavigatorParams & {
   Home: undefined
   Search: {q?: string}
@@ -46,6 +57,7 @@ export type AllNavigatorParams = CommonNavigatorParams & {
   Search: {q?: string}
   NotificationsTab: undefined
   Notifications: undefined
+  MyProfileTab: undefined
 }
 
 // NOTE
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index d520a712f..101b6b833 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -36,8 +36,14 @@ import {FollowState} from 'state/models/cache/my-follows'
 
 const BACK_HITSLOP = {left: 30, top: 30, right: 30, bottom: 30}
 
+interface Props {
+  view: ProfileModel
+  onRefreshAll: () => void
+  hideBackButton?: boolean
+}
+
 export const ProfileHeader = observer(
-  ({view, onRefreshAll}: {view: ProfileModel; onRefreshAll: () => void}) => {
+  ({view, onRefreshAll, hideBackButton = false}: Props) => {
     const pal = usePalette('default')
 
     // loading
@@ -80,17 +86,21 @@ export const ProfileHeader = observer(
 
     // loaded
     // =
-    return <ProfileHeaderLoaded view={view} onRefreshAll={onRefreshAll} />
+    return (
+      <ProfileHeaderLoaded
+        view={view}
+        onRefreshAll={onRefreshAll}
+        hideBackButton={hideBackButton}
+      />
+    )
   },
 )
 
 const ProfileHeaderLoaded = observer(function ProfileHeaderLoaded({
   view,
   onRefreshAll,
-}: {
-  view: ProfileModel
-  onRefreshAll: () => void
-}) {
+  hideBackButton = false,
+}: Props) {
   const pal = usePalette('default')
   const store = useStores()
   const navigation = useNavigation<NavigationProp>()
@@ -336,7 +346,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoaded({
           </View>
         ) : undefined}
       </View>
-      {!isDesktopWeb && (
+      {!isDesktopWeb && !hideBackButton && (
         <TouchableWithoutFeedback onPress={onPressBack} hitSlop={BACK_HITSLOP}>
           <View style={styles.backBtnWrapper}>
             <BlurView style={styles.backBtn} blurType="dark">
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index dfee6f12a..3b4c47ce1 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -96,8 +96,14 @@ export const ProfileScreen = withAuthRequired(
       if (!uiState) {
         return <View />
       }
-      return <ProfileHeader view={uiState.profile} onRefreshAll={onRefresh} />
-    }, [uiState, onRefresh])
+      return (
+        <ProfileHeader
+          view={uiState.profile}
+          onRefreshAll={onRefresh}
+          hideBackButton={route.params.hideBackButton}
+        />
+      )
+    }, [uiState, onRefresh, route.params.hideBackButton])
     const Footer = React.useMemo(() => {
       return uiState.showLoadingMoreFooter ? LoadingMoreFooter : undefined
     }, [uiState.showLoadingMoreFooter])
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
index de36463e1..74e10d6a1 100644
--- a/src/view/shell/Drawer.tsx
+++ b/src/view/shell/Drawer.tsx
@@ -27,6 +27,7 @@ import {
   MagnifyingGlassIcon2,
   MagnifyingGlassIcon2Solid,
   MoonIcon,
+  UserIconSolid,
 } from 'lib/icons'
 import {UserAvatar} from 'view/com/util/UserAvatar'
 import {Text} from 'view/com/util/text/Text'
@@ -45,7 +46,8 @@ export const DrawerContent = observer(() => {
   const store = useStores()
   const navigation = useNavigation<NavigationProp>()
   const {track} = useAnalytics()
-  const {isAtHome, isAtSearch, isAtNotifications} = useNavigationTabState()
+  const {isAtHome, isAtSearch, isAtNotifications, isAtMyProfile} =
+    useNavigationTabState()
 
   // events
   // =
@@ -56,7 +58,7 @@ export const DrawerContent = observer(() => {
       const state = navigation.getState()
       store.shell.closeDrawer()
       if (isWeb) {
-        // @ts-ignore must be Home, Search, or Notifications
+        // @ts-ignore must be Home, Search, Notifications, or MyProfile
         navigation.navigate(tab)
       } else {
         const tabState = getTabState(state, tab)
@@ -65,7 +67,7 @@ export const DrawerContent = observer(() => {
         } else if (tabState === TabState.Inside) {
           navigation.dispatch(StackActions.popToTop())
         } else {
-          // @ts-ignore must be Home, Search, or Notifications
+          // @ts-ignore must be Home, Search, Notifications, or MyProfile
           navigation.navigate(`${tab}Tab`)
         }
       }
@@ -86,10 +88,8 @@ export const DrawerContent = observer(() => {
   )
 
   const onPressProfile = React.useCallback(() => {
-    track('Menu:ItemClicked', {url: 'Profile'})
-    navigation.navigate('Profile', {name: store.me.handle})
-    store.shell.closeDrawer()
-  }, [navigation, track, store.me.handle, store.shell])
+    onPressTab('MyProfile')
+  }, [onPressTab])
 
   const onPressSettings = React.useCallback(() => {
     track('Menu:ItemClicked', {url: 'Settings'})
@@ -211,11 +211,19 @@ export const DrawerContent = observer(() => {
           />
           <MenuItem
             icon={
-              <UserIcon
-                style={pal.text as StyleProp<ViewStyle>}
-                size="26"
-                strokeWidth={1.5}
-              />
+              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"
             onPress={onPressProfile}
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index 59b21968d..4dcaf3eb1 100644
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -5,7 +5,7 @@ import {
   TouchableOpacity,
   View,
 } from 'react-native'
-import {StackActions, useNavigationState} from '@react-navigation/native'
+import {StackActions} from '@react-navigation/native'
 import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {observer} from 'mobx-react-lite'
@@ -21,34 +21,21 @@ import {
   BellIcon,
   BellIconSolid,
   UserIcon,
+  UserIconSolid,
 } 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'
 
 export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
   const store = useStores()
   const pal = usePalette('default')
   const safeAreaInsets = useSafeAreaInsets()
   const {track} = useAnalytics()
-  const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
-    state => {
-      const res = {
-        isAtHome: getTabState(state, 'Home') !== TabState.Outside,
-        isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
-        isAtNotifications:
-          getTabState(state, 'Notifications') !== TabState.Outside,
-      }
-      if (!res.isAtHome && !res.isAtNotifications && !res.isAtSearch) {
-        // HACK for some reason useNavigationState will give us pre-hydration results
-        //      and not update after, so we force isAtHome if all came back false
-        //      -prf
-        res.isAtHome = true
-      }
-      return res
-    },
-  )
+  const {isAtHome, isAtSearch, isAtNotifications, isAtMyProfile} =
+    useNavigationTabState()
 
   const {footerMinimalShellTransform} = useMinimalShellMode()
 
@@ -77,9 +64,8 @@ export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
     [onPressTab],
   )
   const onPressProfile = React.useCallback(() => {
-    track('MobileShell:ProfileButtonPressed')
-    navigation.navigate('Profile', {name: store.me.handle})
-  }, [navigation, track, store.me.handle])
+    onPressTab('MyProfile')
+  }, [onPressTab])
 
   return (
     <Animated.View
@@ -154,11 +140,19 @@ export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
         testID="bottomBarProfileBtn"
         icon={
           <View style={styles.ctrlIconSizingWrapper}>
-            <UserIcon
-              size={28}
-              strokeWidth={1.5}
-              style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
-            />
+            {isAtMyProfile ? (
+              <UserIconSolid
+                size={28}
+                strokeWidth={1.5}
+                style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
+              />
+            ) : (
+              <UserIcon
+                size={28}
+                strokeWidth={1.5}
+                style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
+              />
+            )}
           </View>
         }
         onPress={onPressProfile}