about summary refs log tree commit diff
path: root/src/view/shell/bottom-bar/BottomBar.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/shell/bottom-bar/BottomBar.tsx')
-rw-r--r--src/view/shell/bottom-bar/BottomBar.tsx198
1 files changed, 198 insertions, 0 deletions
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
new file mode 100644
index 000000000..59b21968d
--- /dev/null
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -0,0 +1,198 @@
+import React from 'react'
+import {
+  Animated,
+  GestureResponderEvent,
+  TouchableOpacity,
+  View,
+} from 'react-native'
+import {StackActions, useNavigationState} 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'
+import {Text} from 'view/com/util/text/Text'
+import {useStores} from 'state/index'
+import {useAnalytics} from 'lib/analytics'
+import {clamp} from 'lib/numbers'
+import {
+  HomeIcon,
+  HomeIconSolid,
+  MagnifyingGlassIcon2,
+  MagnifyingGlassIcon2Solid,
+  BellIcon,
+  BellIconSolid,
+  UserIcon,
+} 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'
+
+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 {footerMinimalShellTransform} = useMinimalShellMode()
+
+  const onPressTab = React.useCallback(
+    (tab: string) => {
+      track(`MobileShell:${tab}ButtonPressed`)
+      const state = navigation.getState()
+      const tabState = getTabState(state, tab)
+      if (tabState === TabState.InsideAtRoot) {
+        store.emitScreenSoftReset()
+      } else if (tabState === TabState.Inside) {
+        navigation.dispatch(StackActions.popToTop())
+      } else {
+        navigation.navigate(`${tab}Tab`)
+      }
+    },
+    [store, track, navigation],
+  )
+  const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
+  const onPressSearch = React.useCallback(
+    () => onPressTab('Search'),
+    [onPressTab],
+  )
+  const onPressNotifications = React.useCallback(
+    () => onPressTab('Notifications'),
+    [onPressTab],
+  )
+  const onPressProfile = React.useCallback(() => {
+    track('MobileShell:ProfileButtonPressed')
+    navigation.navigate('Profile', {name: store.me.handle})
+  }, [navigation, track, store.me.handle])
+
+  return (
+    <Animated.View
+      style={[
+        styles.bottomBar,
+        pal.view,
+        pal.border,
+        {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
+        footerMinimalShellTransform,
+      ]}>
+      <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}
+      />
+      <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}
+      />
+      <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={
+          store.me.notifications.unreadCount + store.invitedUsers.numNotifs
+        }
+      />
+      <Btn
+        testID="bottomBarProfileBtn"
+        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({
+  testID,
+  icon,
+  notificationCount,
+  onPress,
+  onLongPress,
+}: {
+  testID?: string
+  icon: JSX.Element
+  notificationCount?: number
+  onPress?: (event: GestureResponderEvent) => void
+  onLongPress?: (event: GestureResponderEvent) => void
+}) {
+  return (
+    <TouchableOpacity
+      testID={testID}
+      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>
+  )
+}