about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-03-06 11:59:06 -0600
committerGitHub <noreply@github.com>2023-03-06 11:59:06 -0600
commitf84a37937609cae7c492e1cc32081376cb452a9a (patch)
treead5042d2843aa0d953d56f849d564b3d8ee8f813 /src
parentc2a931f49c3739a3907bb5c31cd171ed3fbc8d54 (diff)
downloadvoidsky-f84a37937609cae7c492e1cc32081376cb452a9a.tar.zst
Factor out the bottom bar and layer it under the side menu (close #243) (#268)
Diffstat (limited to 'src')
-rw-r--r--src/view/shell/mobile/BottomBar.tsx292
-rw-r--r--src/view/shell/mobile/Menu.tsx6
-rw-r--r--src/view/shell/mobile/index.tsx288
3 files changed, 297 insertions, 289 deletions
diff --git a/src/view/shell/mobile/BottomBar.tsx b/src/view/shell/mobile/BottomBar.tsx
new file mode 100644
index 000000000..2c3ead32a
--- /dev/null
+++ b/src/view/shell/mobile/BottomBar.tsx
@@ -0,0 +1,292 @@
+import React from 'react'
+import {
+  Animated,
+  GestureResponderEvent,
+  StyleSheet,
+  TouchableOpacity,
+  View,
+} from 'react-native'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {observer} from 'mobx-react-lite'
+import {Text} from 'view/com/util/text/Text'
+import {useStores} from 'state/index'
+import {useAnalytics} from 'lib/analytics'
+import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
+import {TabPurpose, TabPurposeMainPath} from 'state/models/navigation'
+import {clamp} from 'lib/numbers'
+import {
+  HomeIcon,
+  HomeIconSolid,
+  MagnifyingGlassIcon2,
+  MagnifyingGlassIcon2Solid,
+  ComposeIcon2,
+  BellIcon,
+  BellIconSolid,
+  UserIcon,
+} from 'lib/icons'
+import {colors} from 'lib/styles'
+import {usePalette} from 'lib/hooks/usePalette'
+
+export const BottomBar = observer(() => {
+  const store = useStores()
+  const pal = usePalette('default')
+  const minimalShellInterp = useAnimatedValue(0)
+  const safeAreaInsets = useSafeAreaInsets()
+  const {track} = useAnalytics()
+
+  React.useEffect(() => {
+    if (store.shell.minimalShellMode) {
+      Animated.timing(minimalShellInterp, {
+        toValue: 1,
+        duration: 100,
+        useNativeDriver: true,
+        isInteraction: false,
+      }).start()
+    } else {
+      Animated.timing(minimalShellInterp, {
+        toValue: 0,
+        duration: 100,
+        useNativeDriver: true,
+        isInteraction: false,
+      }).start()
+    }
+  }, [minimalShellInterp, store.shell.minimalShellMode])
+  const footerMinimalShellTransform = {
+    transform: [{translateY: Animated.multiply(minimalShellInterp, 100)}],
+  }
+
+  const onPressHome = React.useCallback(() => {
+    track('MobileShell:HomeButtonPressed')
+    if (store.nav.tab.fixedTabPurpose === TabPurpose.Default) {
+      if (!store.nav.tab.canGoBack) {
+        store.emitScreenSoftReset()
+      } else {
+        store.nav.tab.fixedTabReset()
+      }
+    } else {
+      store.nav.switchTo(TabPurpose.Default, false)
+      if (store.nav.tab.index === 0) {
+        store.nav.tab.fixedTabReset()
+      }
+    }
+  }, [store, track])
+  const onPressSearch = React.useCallback(() => {
+    track('MobileShell:SearchButtonPressed')
+    if (store.nav.tab.fixedTabPurpose === TabPurpose.Search) {
+      if (!store.nav.tab.canGoBack) {
+        store.emitScreenSoftReset()
+      } else {
+        store.nav.tab.fixedTabReset()
+      }
+    } else {
+      store.nav.switchTo(TabPurpose.Search, false)
+      if (store.nav.tab.index === 0) {
+        store.nav.tab.fixedTabReset()
+      }
+    }
+  }, [store, track])
+  const onPressCompose = React.useCallback(() => {
+    track('MobileShell:ComposeButtonPressed')
+    store.shell.openComposer({})
+  }, [store, track])
+  const onPressNotifications = React.useCallback(() => {
+    track('MobileShell:NotificationsButtonPressed')
+    if (store.nav.tab.fixedTabPurpose === TabPurpose.Notifs) {
+      if (!store.nav.tab.canGoBack) {
+        store.emitScreenSoftReset()
+      } else {
+        store.nav.tab.fixedTabReset()
+      }
+    } else {
+      store.nav.switchTo(TabPurpose.Notifs, false)
+      if (store.nav.tab.index === 0) {
+        store.nav.tab.fixedTabReset()
+      }
+    }
+  }, [store, track])
+  const onPressProfile = React.useCallback(() => {
+    track('MobileShell:ProfileButtonPressed')
+    store.nav.navigate(`/profile/${store.me.handle}`)
+  }, [store, track])
+
+  const isAtHome =
+    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Default]
+  const isAtSearch =
+    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Search]
+  const isAtNotifications =
+    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Notifs]
+
+  return (
+    <Animated.View
+      style={[
+        styles.bottomBar,
+        pal.view,
+        pal.border,
+        {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
+        footerMinimalShellTransform,
+      ]}>
+      <Btn
+        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}
+      />
+      <Btn
+        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}
+      />
+      <Btn
+        icon={
+          <View style={styles.ctrlIconSizingWrapper}>
+            <ComposeIcon2
+              strokeWidth={1.5}
+              size={29}
+              style={[styles.ctrlIcon, pal.text, styles.composeIcon]}
+              backgroundColor={pal.colors.background}
+            />
+          </View>
+        }
+        onPress={onPressCompose}
+      />
+      <Btn
+        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={store.me.notifications.unreadCount}
+      />
+      <Btn
+        icon={
+          <View style={styles.ctrlIconSizingWrapper}>
+            <UserIcon
+              size={28}
+              strokeWidth={1.5}
+              style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
+            />
+          </View>
+        }
+        onPress={onPressProfile}
+      />
+    </Animated.View>
+  )
+})
+
+function Btn({
+  icon,
+  notificationCount,
+  onPress,
+  onLongPress,
+}: {
+  icon: JSX.Element
+  notificationCount?: number
+  onPress?: (event: GestureResponderEvent) => void
+  onLongPress?: (event: GestureResponderEvent) => void
+}) {
+  return (
+    <TouchableOpacity
+      style={styles.ctrl}
+      onPress={onLongPress ? onPress : undefined}
+      onPressIn={onLongPress ? undefined : onPress}
+      onLongPress={onLongPress}>
+      {notificationCount ? (
+        <View style={styles.notificationCount}>
+          <Text style={styles.notificationCountLabel}>{notificationCount}</Text>
+        </View>
+      ) : undefined}
+      {icon}
+    </TouchableOpacity>
+  )
+}
+
+const styles = StyleSheet.create({
+  bottomBar: {
+    position: 'absolute',
+    bottom: 0,
+    left: 0,
+    right: 0,
+    flexDirection: 'row',
+    borderTopWidth: 1,
+    paddingLeft: 5,
+    paddingRight: 10,
+  },
+  ctrl: {
+    flex: 1,
+    paddingTop: 13,
+    paddingBottom: 4,
+  },
+  notificationCount: {
+    position: 'absolute',
+    left: '56%',
+    top: 10,
+    backgroundColor: colors.blue3,
+    paddingHorizontal: 4,
+    paddingBottom: 1,
+    borderRadius: 8,
+    zIndex: 1,
+  },
+  notificationCountLabel: {
+    fontSize: 12,
+    fontWeight: 'bold',
+    color: colors.white,
+  },
+  ctrlIcon: {
+    marginLeft: 'auto',
+    marginRight: 'auto',
+  },
+  ctrlIconSizingWrapper: {
+    height: 27,
+  },
+  homeIcon: {
+    top: 0,
+  },
+  searchIcon: {
+    top: -2,
+  },
+  bellIcon: {
+    top: -2.5,
+  },
+  composeIcon: {
+    top: -4.5,
+  },
+  profileIcon: {
+    top: -4,
+  },
+})
diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx
index 23c09b82c..6c5aa1adb 100644
--- a/src/view/shell/mobile/Menu.tsx
+++ b/src/view/shell/mobile/Menu.tsx
@@ -119,7 +119,6 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
       style={[
         styles.view,
         theme.colorScheme === 'light' ? pal.view : styles.viewDarkMode,
-        store.shell.minimalShellMode && styles.viewMinimalShell,
       ]}>
       <TouchableOpacity
         testID="profileCardButton"
@@ -272,15 +271,12 @@ const styles = StyleSheet.create({
   view: {
     flex: 1,
     paddingTop: 10,
-    paddingBottom: 90,
+    paddingBottom: 50,
     paddingLeft: 30,
   },
   viewDarkMode: {
     backgroundColor: colors.gray8,
   },
-  viewMinimalShell: {
-    paddingBottom: 50,
-  },
 
   profileCardDisplayName: {
     marginTop: 20,
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
index 6ab19d651..b836bb76d 100644
--- a/src/view/shell/mobile/index.tsx
+++ b/src/view/shell/mobile/index.tsx
@@ -1,11 +1,9 @@
-import React, {useState, useEffect} from 'react'
+import React, {useState} from 'react'
 import {observer} from 'mobx-react-lite'
 import {
   Animated,
-  GestureResponderEvent,
   StatusBar,
   StyleSheet,
-  TouchableOpacity,
   TouchableWithoutFeedback,
   useWindowDimensions,
   View,
@@ -14,14 +12,11 @@ import {ScreenContainer, Screen} from 'react-native-screens'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {IconProp} from '@fortawesome/fontawesome-svg-core'
 import {useStores} from 'state/index'
-import {
-  NavigationModel,
-  TabPurpose,
-  TabPurposeMainPath,
-} from 'state/models/navigation'
+import {NavigationModel} from 'state/models/navigation'
 import {match, MatchResult} from '../../routes'
 import {Login} from '../../screens/Login'
 import {Menu} from './Menu'
+import {BottomBar} from './BottomBar'
 import {HorzSwipe} from '../../com/util/gestures/HorzSwipe'
 import {ModalsContainer} from '../../com/modals/Modal'
 import {Lightbox} from '../../com/lightbox/Lightbox'
@@ -29,48 +24,9 @@ import {Text} from '../../com/util/text/Text'
 import {ErrorBoundary} from '../../com/util/ErrorBoundary'
 import {Composer} from './Composer'
 import {s, colors} from 'lib/styles'
-import {clamp} from 'lib/numbers'
-import {
-  HomeIcon,
-  HomeIconSolid,
-  MagnifyingGlassIcon2,
-  MagnifyingGlassIcon2Solid,
-  ComposeIcon2,
-  BellIcon,
-  BellIconSolid,
-  UserIcon,
-} from 'lib/icons'
 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {useTheme} from 'lib/ThemeContext'
 import {usePalette} from 'lib/hooks/usePalette'
-import {useAnalytics} from 'lib/analytics'
-
-const Btn = ({
-  icon,
-  notificationCount,
-  onPress,
-  onLongPress,
-}: {
-  icon: JSX.Element
-  notificationCount?: number
-  onPress?: (event: GestureResponderEvent) => void
-  onLongPress?: (event: GestureResponderEvent) => void
-}) => {
-  return (
-    <TouchableOpacity
-      style={styles.ctrl}
-      onPress={onLongPress ? onPress : undefined}
-      onPressIn={onLongPress ? undefined : onPress}
-      onLongPress={onLongPress}>
-      {notificationCount ? (
-        <View style={styles.notificationCount}>
-          <Text style={styles.notificationCountLabel}>{notificationCount}</Text>
-        </View>
-      ) : undefined}
-      {icon}
-    </TouchableOpacity>
-  )
-}
 
 export const MobileShell: React.FC = observer(() => {
   const theme = useTheme()
@@ -79,87 +35,8 @@ export const MobileShell: React.FC = observer(() => {
   const winDim = useWindowDimensions()
   const [menuSwipingDirection, setMenuSwipingDirection] = useState(0)
   const swipeGestureInterp = useAnimatedValue(0)
-  const minimalShellInterp = useAnimatedValue(0)
   const safeAreaInsets = useSafeAreaInsets()
   const screenRenderDesc = constructScreenRenderDesc(store.nav)
-  const {track} = useAnalytics()
-
-  const onPressHome = () => {
-    track('MobileShell:HomeButtonPressed')
-    if (store.nav.tab.fixedTabPurpose === TabPurpose.Default) {
-      if (!store.nav.tab.canGoBack) {
-        store.emitScreenSoftReset()
-      } else {
-        store.nav.tab.fixedTabReset()
-      }
-    } else {
-      store.nav.switchTo(TabPurpose.Default, false)
-      if (store.nav.tab.index === 0) {
-        store.nav.tab.fixedTabReset()
-      }
-    }
-  }
-  const onPressSearch = () => {
-    track('MobileShell:SearchButtonPressed')
-    if (store.nav.tab.fixedTabPurpose === TabPurpose.Search) {
-      if (!store.nav.tab.canGoBack) {
-        store.emitScreenSoftReset()
-      } else {
-        store.nav.tab.fixedTabReset()
-      }
-    } else {
-      store.nav.switchTo(TabPurpose.Search, false)
-      if (store.nav.tab.index === 0) {
-        store.nav.tab.fixedTabReset()
-      }
-    }
-  }
-  const onPressCompose = () => {
-    track('MobileShell:ComposeButtonPressed')
-    store.shell.openComposer({})
-  }
-  const onPressNotifications = () => {
-    track('MobileShell:NotificationsButtonPressed')
-    if (store.nav.tab.fixedTabPurpose === TabPurpose.Notifs) {
-      if (!store.nav.tab.canGoBack) {
-        store.emitScreenSoftReset()
-      } else {
-        store.nav.tab.fixedTabReset()
-      }
-    } else {
-      store.nav.switchTo(TabPurpose.Notifs, false)
-      if (store.nav.tab.index === 0) {
-        store.nav.tab.fixedTabReset()
-      }
-    }
-  }
-  const onPressProfile = () => {
-    track('MobileShell:ProfileButtonPressed')
-    store.nav.navigate(`/profile/${store.me.handle}`)
-  }
-
-  // minimal shell animation
-  // =
-  useEffect(() => {
-    if (store.shell.minimalShellMode) {
-      Animated.timing(minimalShellInterp, {
-        toValue: 1,
-        duration: 100,
-        useNativeDriver: true,
-        isInteraction: false,
-      }).start()
-    } else {
-      Animated.timing(minimalShellInterp, {
-        toValue: 0,
-        duration: 100,
-        useNativeDriver: true,
-        isInteraction: false,
-      }).start()
-    }
-  }, [minimalShellInterp, store.shell.minimalShellMode])
-  const footerMinimalShellTransform = {
-    transform: [{translateY: Animated.multiply(minimalShellInterp, 100)}],
-  }
 
   // navigation swipes
   // =
@@ -231,13 +108,6 @@ export const MobileShell: React.FC = observer(() => {
           }),
         }
       : undefined
-  // TODO
-  // const tabMenuTransform = {
-  //   transform: [{translateY: Animated.multiply(tabMenuInterp, -320)}],
-  // }
-  // const newTabTransform = {
-  //   transform: [{scale: newTabInterp}],
-  // }
 
   if (store.hackUpgradeNeeded) {
     return (
@@ -286,13 +156,6 @@ export const MobileShell: React.FC = observer(() => {
     )
   }
 
-  const isAtHome =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Default]
-  const isAtSearch =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Search]
-  const isAtNotifications =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Notifs]
-
   const screenBg = {
     backgroundColor: theme.colorScheme === 'dark' ? colors.gray7 : colors.gray1,
   }
@@ -360,6 +223,7 @@ export const MobileShell: React.FC = observer(() => {
               },
             )}
           </ScreenContainer>
+          <BottomBar />
           {isMenuActive || menuSwipingDirection !== 0 ? (
             <TouchableWithoutFeedback
               onPress={() => store.shell.setMainMenuOpen(false)}>
@@ -373,95 +237,6 @@ export const MobileShell: React.FC = observer(() => {
           )}
         </HorzSwipe>
       </View>
-      <Animated.View
-        style={[
-          styles.bottomBar,
-          pal.view,
-          pal.border,
-          {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
-          footerMinimalShellTransform,
-        ]}>
-        <Btn
-          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}
-        />
-        <Btn
-          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}
-        />
-        <Btn
-          icon={
-            <View style={styles.ctrlIconSizingWrapper}>
-              <ComposeIcon2
-                strokeWidth={1.5}
-                size={29}
-                style={[styles.ctrlIcon, pal.text, styles.composeIcon]}
-                backgroundColor={pal.colors.background}
-              />
-            </View>
-          }
-          onPress={onPressCompose}
-        />
-        <Btn
-          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={store.me.notifications.unreadCount}
-        />
-        <Btn
-          icon={
-            <View style={styles.ctrlIconSizingWrapper}>
-              <UserIcon
-                size={28}
-                strokeWidth={1.5}
-                style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
-              />
-            </View>
-          }
-          onPress={onPressProfile}
-        />
-      </Animated.View>
       <ModalsContainer />
       <Lightbox />
       <Composer
@@ -563,59 +338,4 @@ const styles = StyleSheet.create({
   topBarProtectorDark: {
     backgroundColor: colors.black,
   },
-  bottomBar: {
-    position: 'absolute',
-    bottom: 0,
-    left: 0,
-    right: 0,
-    flexDirection: 'row',
-    borderTopWidth: 1,
-    paddingLeft: 5,
-    paddingRight: 10,
-  },
-  ctrl: {
-    flex: 1,
-    paddingTop: 13,
-    paddingBottom: 4,
-  },
-  notificationCount: {
-    position: 'absolute',
-    left: '56%',
-    top: 10,
-    backgroundColor: colors.blue3,
-    paddingHorizontal: 4,
-    paddingBottom: 1,
-    borderRadius: 8,
-    zIndex: 1,
-  },
-  notificationCountLabel: {
-    fontSize: 12,
-    fontWeight: 'bold',
-    color: colors.white,
-  },
-  ctrlIcon: {
-    marginLeft: 'auto',
-    marginRight: 'auto',
-  },
-  ctrlIconSizingWrapper: {
-    height: 27,
-  },
-  inactive: {
-    color: colors.gray3,
-  },
-  homeIcon: {
-    top: 0,
-  },
-  searchIcon: {
-    top: -2,
-  },
-  bellIcon: {
-    top: -2.5,
-  },
-  composeIcon: {
-    top: -4.5,
-  },
-  profileIcon: {
-    top: -4,
-  },
 })