Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC/Top level bottom tab bar #155

Merged
merged 2 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4621,6 +4621,7 @@ const CONST = {
/** These action types are custom for RootNavigator */
SWITCH_POLICY_ID: 'SWITCH_POLICY_ID',
DISMISS_MODAL: 'DISMISS_MODAL',
OPEN_WORKSPACE_SPLIT: 'OPEN_WORKSPACE_SPLIT',
},
},
TIME_PERIOD: {
Expand Down
28 changes: 15 additions & 13 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import Log from '@libs/Log';
import NavBarManager from '@libs/NavBarManager';
import getCurrentUrl from '@libs/Navigation/currentUrl';
import {isOnboardingFlowName} from '@libs/Navigation/helpers';
import SIDEBAR_TO_SPLIT from '@libs/Navigation/linkingConfig/RELATIONS/SIDEBAR_TO_SPLIT';
import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation';
import Presentation from '@libs/Navigation/PlatformStackNavigation/navigationOptions/presentation';
Expand Down Expand Up @@ -59,6 +58,7 @@ import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type ReactComponentModule from '@src/types/utils/ReactComponentModule';
import createResponsiveStackNavigator from './createResponsiveStackNavigator';
import {workspaceSplitsWithoutEnteringAnimation} from './createResponsiveStackNavigator/GetStateForActionHandlers';
import defaultScreenOptions from './defaultScreenOptions';
import ExplanationModalNavigator from './Navigators/ExplanationModalNavigator';
import FeatureTrainingModalNavigator from './Navigators/FeatureTrainingModalNavigator';
Expand Down Expand Up @@ -367,21 +367,23 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
}, []);

// Animation is disabled when navigating to the sidebar screen
const getSplitNavigatorOptions = (route: RouteProp<AuthScreensParamList>) => {
if (!shouldUseNarrowLayout || !route?.params) {
const getWorkspaceSplitNavigatorOptions = ({route}: {route: RouteProp<AuthScreensParamList>}) => {
// We don't need to do anything special for the wide screen.
if (!shouldUseNarrowLayout) {
return rootNavigatorOptions.fullScreen;
}

const screenName = 'screen' in route.params ? route.params.screen : undefined;

if (!screenName) {
return rootNavigatorOptions.fullScreen;
}

const animationEnabled = !Object.keys(SIDEBAR_TO_SPLIT).includes(screenName);
// On the narrow screen, we want to animate this navigator if it is opened from the settings split.
// If it is opened from other tab, we don't want to animate it on the entry.
// There is a hook inside the workspace navigator that changes animation to SLIDE_FROM_RIGHT after entering.
// This way it can be animated properly when going back to the settings split.
const animationEnabled = !workspaceSplitsWithoutEnteringAnimation.has(route.key);

return {
...rootNavigatorOptions.fullScreen,

// Allow swipe to go back from this split navigator to the settings navigator.
gestureEnabled: true,
animation: animationEnabled ? Animations.SLIDE_FROM_RIGHT : Animations.NONE,
};
};
Expand All @@ -393,12 +395,12 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
{/* This have to be the first navigator in auth screens. */}
<RootStack.Screen
name={NAVIGATORS.REPORTS_SPLIT_NAVIGATOR}
options={({route}) => getSplitNavigatorOptions(route)}
options={rootNavigatorOptions.fullScreen}
getComponent={loadReportSplitNavigator}
/>
<RootStack.Screen
name={NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR}
options={({route}) => getSplitNavigatorOptions(route)}
options={rootNavigatorOptions.fullScreen}
getComponent={loadSettingsSplitNavigator}
/>
<RootStack.Screen
Expand All @@ -409,7 +411,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
/>
<RootStack.Screen
name={NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR}
options={({route}) => getSplitNavigatorOptions(route)}
options={getWorkspaceSplitNavigatorOptions}
getComponent={loadWorkspaceSplitNavigator}
/>
<RootStack.Screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import React from 'react';
import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen';
import createSplitNavigator from '@libs/Navigation/AppNavigator/createSplitNavigator';
import useRootNavigatorOptions from '@libs/Navigation/AppNavigator/useRootNavigatorOptions';
import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation';
import type {PlatformStackNavigationOptions} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsSplitNavigatorParamList} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';
import type ReactComponentModule from '@src/types/utils/ReactComponentModule';
Expand Down Expand Up @@ -45,18 +43,11 @@ function SettingsSplitNavigator() {
options={rootNavigatorOptions.homeScreen}
/>
{Object.entries(CENTRAL_PANE_SETTINGS_SCREENS).map(([screenName, componentGetter]) => {
const options: PlatformStackNavigationOptions = {animation: undefined};

if (screenName === SCREENS.SETTINGS.WORKSPACES) {
options.animation = Animations.NONE;
}

return (
<Split.Screen
key={screenName}
name={screenName as keyof Screens}
getComponent={componentGetter}
options={options}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {useRoute} from '@react-navigation/native';
import React from 'react';
import React, {useEffect} from 'react';
import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen';
import {workspaceSplitsWithoutEnteringAnimation} from '@libs/Navigation/AppNavigator/createResponsiveStackNavigator/GetStateForActionHandlers';
import createSplitNavigator from '@libs/Navigation/AppNavigator/createSplitNavigator';
import useRootNavigatorOptions from '@libs/Navigation/AppNavigator/useRootNavigatorOptions';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {AuthScreensParamList, WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
import type NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';
import type ReactComponentModule from '@src/types/utils/ReactComponentModule';

Expand Down Expand Up @@ -31,10 +34,26 @@ const CENTRAL_PANE_WORKSPACE_SCREENS = {

const Split = createSplitNavigator<WorkspaceSplitNavigatorParamList>();

function WorkspaceNavigator() {
const route = useRoute();
function WorkspaceSplitNavigator({route, navigation}: PlatformStackScreenProps<AuthScreensParamList, typeof NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR>) {
const rootNavigatorOptions = useRootNavigatorOptions();

useEffect(() => {
const unsubscribe = navigation.addListener('transitionEnd', () => {
// We want to call this function only once.
unsubscribe();

// If we open this screen from a different tab, then it won't have animation.
if (!workspaceSplitsWithoutEnteringAnimation.has(route.key)) {
return;
}

// We want ot set animation after mounting so it will animate on going UP to the settings split.
navigation.setOptions({animation: Animations.SLIDE_FROM_RIGHT});
});

return unsubscribe;
}, [navigation, route.key]);

return (
<FocusTrapForScreens>
<Split.Navigator
Expand All @@ -60,7 +79,7 @@ function WorkspaceNavigator() {
);
}

WorkspaceNavigator.displayName = 'WorkspaceNavigator';
WorkspaceSplitNavigator.displayName = 'WorkspaceSplitNavigator';

export {CENTRAL_PANE_WORKSPACE_SCREENS};
export default WorkspaceNavigator;
export default WorkspaceSplitNavigator;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {memo, useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithFeedback} from '@components/Pressable';
Expand All @@ -9,11 +10,14 @@ import Text from '@components/Text';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useCurrentReportID from '@hooks/useCurrentReportID';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import {getPreservedSplitNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveSplitNavigatorState';
import {isFullScreenName} from '@libs/Navigation/helpers';
import Navigation from '@libs/Navigation/Navigation';
import type {AuthScreensParamList, RootStackParamList, State} from '@libs/Navigation/types';
import type {AuthScreensParamList, RootStackParamList, State, WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as SearchQueryUtils from '@libs/SearchQueryUtils';
import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
Expand All @@ -23,13 +27,22 @@ import BottomTabAvatar from '@pages/home/sidebar/BottomTabAvatar';
import BottomTabBarFloatingActionButton from '@pages/home/sidebar/BottomTabBarFloatingActionButton';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import DebugTabView from './DebugTabView';

const BOTTOM_TABS = {
HOME: 'HOME',
SEARCH: 'SEARCH',
SETTINGS: 'SETTINGS',
} as const;

type BottomTabs = ValueOf<typeof BOTTOM_TABS>;

type BottomTabBarProps = {
selectedTab: string | undefined;
selectedTab: BottomTabs;
};

/**
Expand Down Expand Up @@ -73,6 +86,7 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [chatTabBrickRoad, setChatTabBrickRoad] = useState<BrickRoad>(() =>
getChatTabBrickRoad(activeWorkspaceID, currentReportID, reports, betas, policies, priorityMode, transactionViolations),
);
Expand All @@ -84,15 +98,15 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
}, [activeWorkspaceID, transactionViolations, reports, reportActions, betas, policies, priorityMode, currentReportID]);

const navigateToChats = useCallback(() => {
if (selectedTab === SCREENS.HOME) {
if (selectedTab === BOTTOM_TABS.HOME) {
return;
}

Navigation.navigate(ROUTES.HOME);
}, [selectedTab]);

const navigateToSearch = useCallback(() => {
if (selectedTab === SCREENS.SEARCH.CENTRAL_PANE) {
if (selectedTab === BOTTOM_TABS.SEARCH) {
return;
}
interceptAnonymousUser(() => {
Expand All @@ -119,6 +133,65 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
});
}, [activeWorkspaceID, selectedTab]);

const showSettingsPage = useCallback(() => {
const rootState = navigationRef.getRootState();
const topmostFullScreenRoute = rootState.routes.findLast((route) => isFullScreenName(route.name));

if (!topmostFullScreenRoute) {
return;
}

const lastRouteOfTopmostFullScreenRoute = 'state' in topmostFullScreenRoute ? topmostFullScreenRoute.state?.routes.at(-1) : undefined;

if (lastRouteOfTopmostFullScreenRoute && lastRouteOfTopmostFullScreenRoute.name === SCREENS.SETTINGS.WORKSPACES && shouldUseNarrowLayout) {
Navigation.goBack(ROUTES.SETTINGS);
return;
}

if (topmostFullScreenRoute.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR) {
Navigation.goBack(ROUTES.SETTINGS);
return;
}

interceptAnonymousUser(() => {
const lastSettingsOrWorkspaceNavigatorRoute = rootState.routes.findLast(
(rootRoute) => rootRoute.name === NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR || rootRoute.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR,
);

// If there is a workspace navigator route, then we should open the workspace initial screen as it should be "remembered".
if (lastSettingsOrWorkspaceNavigatorRoute?.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR) {
const state = lastSettingsOrWorkspaceNavigatorRoute.state ?? getPreservedSplitNavigatorState(lastSettingsOrWorkspaceNavigatorRoute.key);
const params = state?.routes.at(0)?.params as WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL];

// Screens of this navigator should always have policyID
if (params.policyID) {
// This action will put settings split under the workspace split to make sure that we can swipe back to settings split.
navigationRef.dispatch({
type: CONST.NAVIGATION.ACTION_TYPE.OPEN_WORKSPACE_SPLIT,
payload: {
policyID: params.policyID,
},
});
}
return;
}

// If there is settings workspace screen in the settings navigator, then we should open the settings workspaces as it should be "remembered".
if (
lastSettingsOrWorkspaceNavigatorRoute &&
lastSettingsOrWorkspaceNavigatorRoute.state &&
lastSettingsOrWorkspaceNavigatorRoute.state.routes.at(-1)?.name === SCREENS.SETTINGS.WORKSPACES
) {
Navigation.navigate(ROUTES.SETTINGS_WORKSPACES);
return;
}

// Otherwise we should simply open the settings navigator.
// This case also covers if there is no route to remember.
Navigation.navigate(ROUTES.SETTINGS);
});
}, [shouldUseNarrowLayout]);

return (
<>
{!!user?.isDebugModeEnabled && (
Expand All @@ -145,7 +218,7 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
<View>
<Icon
src={Expensicons.Inbox}
fill={selectedTab === SCREENS.HOME ? theme.iconMenu : theme.icon}
fill={selectedTab === BOTTOM_TABS.HOME ? theme.iconMenu : theme.icon}
width={variables.iconBottomBar}
height={variables.iconBottomBar}
/>
Expand All @@ -154,7 +227,13 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
)}
</View>
<Text
style={[styles.textSmall, styles.textAlignCenter, styles.mt1Half, selectedTab === SCREENS.HOME ? styles.textBold : styles.textSupporting, styles.bottomTabBarLabel]}
style={[
styles.textSmall,
styles.textAlignCenter,
styles.mt1Half,
selectedTab === BOTTOM_TABS.HOME ? styles.textBold : styles.textSupporting,
styles.bottomTabBarLabel,
]}
>
{translate('common.inbox')}
</Text>
Expand All @@ -169,7 +248,7 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
<View>
<Icon
src={Expensicons.MoneySearch}
fill={selectedTab === SCREENS.SEARCH.CENTRAL_PANE ? theme.iconMenu : theme.icon}
fill={selectedTab === BOTTOM_TABS.SEARCH ? theme.iconMenu : theme.icon}
width={variables.iconBottomBar}
height={variables.iconBottomBar}
/>
Expand All @@ -179,14 +258,17 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
styles.textSmall,
styles.textAlignCenter,
styles.mt1Half,
selectedTab === SCREENS.SEARCH.CENTRAL_PANE ? styles.textBold : styles.textSupporting,
selectedTab === BOTTOM_TABS.SEARCH ? styles.textBold : styles.textSupporting,
styles.bottomTabBarLabel,
]}
>
{translate('common.search')}
</Text>
</PressableWithFeedback>
<BottomTabAvatar isSelected={selectedTab === SCREENS.SETTINGS.ROOT} />
<BottomTabAvatar
isSelected={selectedTab === BOTTOM_TABS.SETTINGS}
onPress={showSettingsPage}
/>
<View style={[styles.flex1, styles.bottomTabBarItem]}>
<BottomTabBarFloatingActionButton />
</View>
Expand All @@ -198,3 +280,5 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
BottomTabBar.displayName = 'BottomTabBar';

export default memo(BottomTabBar);
export {BOTTOM_TABS};
export type {BottomTabs};
Loading
Loading