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,
- },
})
|