From 0d92dd8306498b981e59fc2677c11fe4b2ec4f0b Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 9 Mar 2023 21:16:58 -0600 Subject: [PATCH] Update mobile menu to be react-native drawer --- src/view/screens.tsx | 20 +- src/view/shell/BottomBar.tsx | 2 +- src/view/shell/Drawer.tsx | 408 +++++++++++++++++++++++++++++++++ src/view/shell/mobile/Menu.tsx | 354 ---------------------------- 4 files changed, 416 insertions(+), 368 deletions(-) create mode 100644 src/view/shell/Drawer.tsx delete mode 100644 src/view/shell/mobile/Menu.tsx diff --git a/src/view/screens.tsx b/src/view/screens.tsx index 3d49b8eabe..da9fbedb2d 100644 --- a/src/view/screens.tsx +++ b/src/view/screens.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import {View, Text} from 'react-native' import {NavigationContainer} from '@react-navigation/native' import {createNativeStackNavigator} from '@react-navigation/native-stack' import {createDrawerNavigator} from '@react-navigation/drawer' @@ -12,6 +11,7 @@ import { State, } from 'lib/routes/types' +import {Drawer} from './shell/Drawer' import {BottomBar} from './shell/BottomBar' import {HomeScreen} from './screens/Home' @@ -137,15 +137,6 @@ function buildStateObject(stack: string, route: string, params: RouteParams) { } } -function DrawerContent() { - // TODO - return ( - - Drawer - - ) -} - function commonScreens(Stack: ReturnType) { return ( <> @@ -166,9 +157,10 @@ function commonScreens(Stack: ReturnType) { } function HomeDrawerNavigator() { + const drawerContent = React.useCallback(props => , []) return ( @@ -190,9 +182,10 @@ function HomeStackNavigator() { } function NotificationsDrawerNavigator() { + const drawerContent = React.useCallback(props => , []) return ( , []) return ( diff --git a/src/view/shell/BottomBar.tsx b/src/view/shell/BottomBar.tsx index 100866f747..273eb01879 100644 --- a/src/view/shell/BottomBar.tsx +++ b/src/view/shell/BottomBar.tsx @@ -14,7 +14,6 @@ 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, @@ -28,6 +27,7 @@ import { import {colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' +// TODO move to lib function currentRoute(state) { let node = state.routes[state.index] while (node.state?.routes && typeof node.state?.index === 'number') { diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx new file mode 100644 index 0000000000..6eae9a38bc --- /dev/null +++ b/src/view/shell/Drawer.tsx @@ -0,0 +1,408 @@ +import React from 'react' +import { + Linking, + SafeAreaView, + StyleProp, + StyleSheet, + TouchableOpacity, + View, + ViewStyle, +} from 'react-native' +import { + DrawerContentComponentProps, + useDrawerStatus, +} from '@react-navigation/drawer' +import {StackActions} from '@react-navigation/native' +import {observer} from 'mobx-react-lite' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import {s, colors} from 'lib/styles' +import {FEEDBACK_FORM_URL} from 'lib/constants' +import {useStores} from 'state/index' +import { + HomeIcon, + HomeIconSolid, + BellIcon, + BellIconSolid, + UserIcon, + CogIcon, + MagnifyingGlassIcon2, + MagnifyingGlassIcon2Solid, + MoonIcon, +} from 'lib/icons' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {Text} from 'view/com/util/text/Text' +import {useTheme} from 'lib/ThemeContext' +import {usePalette} from 'lib/hooks/usePalette' +import {useAnalytics} from 'lib/analytics' +import {pluralize} from 'lib/strings/helpers' + +// TODO move to lib +function currentRoute(state) { + let node = state.routes[state.index] + while (node.state?.routes && typeof node.state?.index === 'number') { + node = node.state?.routes[node.state?.index] + } + return node +} + +export const Drawer = observer( + ({state, navigation}: DrawerContentComponentProps) => { + const theme = useTheme() + const pal = usePalette('default') + const store = useStores() + const {track} = useAnalytics() + const isDrawerOpen = useDrawerStatus() === 'open' + + // events + // = + + React.useEffect(() => { + console.log( + 'Drawer', + isDrawerOpen ? 'minimizing' : 'unminimizing', + 'shell', + ) + store.shell.setMinimalShellMode(isDrawerOpen) + }, [isDrawerOpen, store]) + + const onPressTab = React.useCallback( + (tab: string) => { + track('Menu:ItemClicked', {url: tab}) + const state = navigation.getState() + const curr = currentRoute(state).name + if (curr === tab || curr === `${tab}Stack`) { + store.emitScreenSoftReset() + } else if (state.routes[state.index].name === `${tab}Stack`) { + navigation.dispatch(StackActions.popToTop()) + } else { + navigation.navigate(`${tab}Stack`) + } + }, + [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('Menu:ItemClicked', {url: 'Profile'}) + navigation.navigate('Profile', {name: store.me.handle}) + }, [navigation, track, store.me.handle]) + + const onPressSettings = React.useCallback(() => { + track('Menu:ItemClicked', {url: 'Settings'}) + navigation.navigate('Settings') + }, [navigation, track]) + + const onPressFeedback = () => { + track('Menu:FeedbackClicked') + Linking.openURL(FEEDBACK_FORM_URL) + } + + // rendering + // = + + const MenuItem = ({ + icon, + label, + count, + bold, + onPress, + }: { + icon: JSX.Element + label: string + count?: number + bold?: boolean + onPress: () => void + }) => ( + + + {icon} + {count ? ( + + {count} + + ) : undefined} + + + {label} + + + ) + + const onDarkmodePress = () => { + track('Menu:ItemClicked', {url: '/darkmode'}) + store.shell.setDarkMode(!store.shell.darkMode) + } + + const curr = currentRoute(navigation.getState()).name + const isAtHome = curr === 'HomeStack' || curr === 'Home' + const isAtSearch = curr === 'SearchStack' || curr === 'Search' + const isAtNotifications = + curr === 'NotificationsStack' || curr === 'Notifications' + + return ( + + + onNavigate(`/profile/${store.me.handle}`)}> + + + {store.me.displayName || store.me.handle} + + + @{store.me.handle} + + + + {store.me.followersCount || 0} + {' '} + {pluralize(store.me.followersCount || 0, 'follower')} ·{' '} + + {store.me.followsCount || 0} + {' '} + following + + + + + } + size={24} + strokeWidth={1.7} + /> + ) : ( + } + size={24} + strokeWidth={1.7} + /> + ) + } + label="Search" + bold={isAtSearch} + onPress={onPressSearch} + /> + } + size="24" + strokeWidth={3.25} + fillOpacity={1} + /> + ) : ( + } + size="24" + strokeWidth={3.25} + /> + ) + } + label="Home" + bold={isAtHome} + onPress={onPressHome} + /> + } + size="24" + strokeWidth={1.7} + fillOpacity={1} + /> + ) : ( + } + size="24" + strokeWidth={1.7} + /> + ) + } + label="Notifications" + count={store.me.notifications.unreadCount} + bold={isAtNotifications} + onPress={onPressNotifications} + /> + } + size="26" + strokeWidth={1.5} + /> + } + label="Profile" + onPress={onPressProfile} + /> + } + size="26" + strokeWidth={1.75} + /> + } + label="Settings" + onPress={onPressSettings} + /> + + + + + } + strokeWidth={2} + /> + + + + + Feedback + + + + + + ) + }, +) + +const styles = StyleSheet.create({ + view: { + flex: 1, + paddingTop: 20, + paddingBottom: 50, + paddingLeft: 30, + }, + viewDarkMode: { + backgroundColor: '#1B1919', + }, + + profileCardDisplayName: { + marginTop: 20, + paddingRight: 30, + }, + profileCardHandle: { + marginTop: 4, + paddingRight: 30, + }, + profileCardFollowers: { + marginTop: 16, + paddingRight: 30, + }, + + menuItem: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 16, + paddingRight: 10, + }, + menuItemIconWrapper: { + width: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', + marginRight: 12, + }, + menuItemCount: { + position: 'absolute', + right: -6, + top: -2, + backgroundColor: colors.red3, + paddingHorizontal: 4, + paddingBottom: 1, + borderRadius: 6, + }, + menuItemCountLabel: { + fontSize: 12, + fontWeight: 'bold', + color: colors.white, + }, + + footer: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingRight: 30, + paddingTop: 80, + }, + footerBtn: { + flexDirection: 'row', + alignItems: 'center', + padding: 10, + borderRadius: 25, + }, + footerBtnDarkMode: { + backgroundColor: colors.black, + }, + footerBtnFeedback: { + paddingHorizontal: 24, + }, + footerBtnFeedbackLight: { + backgroundColor: '#DDEFFF', + }, + footerBtnFeedbackDark: { + backgroundColor: colors.blue6, + }, +}) diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx deleted file mode 100644 index 927e712e11..0000000000 --- a/src/view/shell/mobile/Menu.tsx +++ /dev/null @@ -1,354 +0,0 @@ -import React from 'react' -import { - Linking, - StyleProp, - StyleSheet, - TouchableOpacity, - View, - ViewStyle, -} from 'react-native' -import {observer} from 'mobx-react-lite' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {s, colors} from 'lib/styles' -import {FEEDBACK_FORM_URL} from 'lib/constants' -import {useStores} from 'state/index' -import { - HomeIcon, - HomeIconSolid, - BellIcon, - BellIconSolid, - UserIcon, - CogIcon, - MagnifyingGlassIcon2, - MagnifyingGlassIcon2Solid, - MoonIcon, -} from 'lib/icons' -import {TabPurpose, TabPurposeMainPath} from 'state/models/navigation' -import {UserAvatar} from '../../com/util/UserAvatar' -import {Text} from '../../com/util/text/Text' -import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics' -import {pluralize} from 'lib/strings/helpers' - -export const Menu = observer(({onClose}: {onClose: () => void}) => { - const theme = useTheme() - const pal = usePalette('default') - const store = useStores() - const {track} = useAnalytics() - - // events - // = - - const onNavigate = (url: string) => { - track('Menu:ItemClicked', {url}) - - onClose() - if (url === TabPurposeMainPath[TabPurpose.Notifs]) { - store.nav.switchTo(TabPurpose.Notifs, true) - } else if (url === TabPurposeMainPath[TabPurpose.Search]) { - store.nav.switchTo(TabPurpose.Search, true) - } else { - store.nav.switchTo(TabPurpose.Default, true) - if (url !== '/') { - store.nav.navigate(url) - } - } - } - - const onPressFeedback = () => { - track('Menu:FeedbackClicked') - Linking.openURL(FEEDBACK_FORM_URL) - } - - // rendering - // = - - const MenuItem = ({ - icon, - label, - count, - url, - bold, - onPress, - }: { - icon: JSX.Element - label: string - count?: number - url?: string - bold?: boolean - onPress?: () => void - }) => ( - onNavigate(url || '/')}> - - {icon} - {count ? ( - - {count} - - ) : undefined} - - - {label} - - - ) - - const onDarkmodePress = () => { - track('Menu:ItemClicked', {url: '/darkmode'}) - store.shell.setDarkMode(!store.shell.darkMode) - } - - 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 ( - - onNavigate(`/profile/${store.me.handle}`)}> - - - {store.me.displayName || store.me.handle} - - - @{store.me.handle} - - - - {store.me.followersCount || 0} - {' '} - {pluralize(store.me.followersCount || 0, 'follower')} ·{' '} - - {store.me.followsCount || 0} - {' '} - following - - - - - } - size={24} - strokeWidth={1.7} - /> - ) : ( - } - size={24} - strokeWidth={1.7} - /> - ) - } - label="Search" - url="/search" - bold={isAtSearch} - /> - } - size="24" - strokeWidth={3.25} - fillOpacity={1} - /> - ) : ( - } - size="24" - strokeWidth={3.25} - /> - ) - } - label="Home" - url="/" - bold={isAtHome} - /> - } - size="24" - strokeWidth={1.7} - fillOpacity={1} - /> - ) : ( - } - size="24" - strokeWidth={1.7} - /> - ) - } - label="Notifications" - url="/notifications" - count={store.me.notifications.unreadCount} - bold={isAtNotifications} - /> - } - size="26" - strokeWidth={1.5} - /> - } - label="Profile" - url={`/profile/${store.me.handle}`} - /> - } - size="26" - strokeWidth={1.75} - /> - } - label="Settings" - url="/settings" - /> - - - - - } - strokeWidth={2} - /> - - - - - Feedback - - - - - ) -}) - -const styles = StyleSheet.create({ - view: { - flex: 1, - paddingTop: 20, - paddingBottom: 50, - paddingLeft: 30, - }, - viewDarkMode: { - backgroundColor: '#1B1919', - }, - - profileCardDisplayName: { - marginTop: 20, - paddingRight: 20, - }, - profileCardHandle: { - marginTop: 4, - paddingRight: 20, - }, - profileCardFollowers: { - marginTop: 16, - paddingRight: 20, - }, - - menuItem: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 16, - paddingRight: 10, - }, - menuItemIconWrapper: { - width: 24, - height: 24, - alignItems: 'center', - justifyContent: 'center', - marginRight: 12, - }, - menuItemCount: { - position: 'absolute', - right: -6, - top: -2, - backgroundColor: colors.red3, - paddingHorizontal: 4, - paddingBottom: 1, - borderRadius: 6, - }, - menuItemCountLabel: { - fontSize: 12, - fontWeight: 'bold', - color: colors.white, - }, - - footer: { - flexDirection: 'row', - justifyContent: 'space-between', - paddingRight: 30, - paddingTop: 80, - }, - footerBtn: { - flexDirection: 'row', - alignItems: 'center', - padding: 10, - borderRadius: 25, - }, - footerBtnDarkMode: { - backgroundColor: colors.black, - }, - footerBtnFeedback: { - paddingHorizontal: 24, - }, - footerBtnFeedbackLight: { - backgroundColor: '#DDEFFF', - }, - footerBtnFeedbackDark: { - backgroundColor: colors.blue6, - }, -})