about summary refs log tree commit diff
path: root/src/view/shell/BottomBar.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/shell/BottomBar.tsx')
-rw-r--r--src/view/shell/BottomBar.tsx255
1 files changed, 255 insertions, 0 deletions
diff --git a/src/view/shell/BottomBar.tsx b/src/view/shell/BottomBar.tsx
new file mode 100644
index 000000000..18b06968f
--- /dev/null
+++ b/src/view/shell/BottomBar.tsx
@@ -0,0 +1,255 @@
+import React from 'react'
+import {
+  Animated,
+  GestureResponderEvent,
+  StyleSheet,
+  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 {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
+import {clamp} from 'lib/numbers'
+import {
+  HomeIcon,
+  HomeIconSolid,
+  MagnifyingGlassIcon2,
+  MagnifyingGlassIcon2Solid,
+  BellIcon,
+  BellIconSolid,
+  UserIcon,
+} from 'lib/icons'
+import {colors} from 'lib/styles'
+import {usePalette} from 'lib/hooks/usePalette'
+import {getTabState, TabState} from 'lib/routes/helpers'
+
+export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
+  const store = useStores()
+  const pal = usePalette('default')
+  const minimalShellInterp = useAnimatedValue(0)
+  const safeAreaInsets = useSafeAreaInsets()
+  const {track} = useAnalytics()
+  const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
+    state => {
+      return {
+        isAtHome: getTabState(state, 'Home') !== TabState.Outside,
+        isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
+        isAtNotifications:
+          getTabState(state, 'Notifications') !== TabState.Outside,
+      }
+    },
+  )
+
+  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 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
+        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={
+          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: '52%',
+    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,
+  },
+  profileIcon: {
+    top: -4,
+  },
+})