Skip to content

Commit

Permalink
Merge pull request #40280 from software-mansion-labs/search-v1/nav-logic
Browse files Browse the repository at this point in the history
[Search v1] Implement Search navigation logic
  • Loading branch information
luacmartins authored Apr 18, 2024
2 parents a96cc01 + 3b7a83d commit 13a01fb
Show file tree
Hide file tree
Showing 25 changed files with 274 additions and 77 deletions.
7 changes: 7 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3299,6 +3299,13 @@ const CONST = {
SCAN: 'scan',
DISTANCE: 'distance',
},
TAB_SEARCH: {
ALL: 'all',
SENT: 'sent',
DRAFTS: 'drafts',
WAITING_ON_YOU: 'waitingOnYou',
FINISHED: 'finished',
},
STATUS_TEXT_MAX_LENGTH: 100,

DROPDOWN_BUTTON_SIZE: {
Expand Down
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const ROUTES = {

ALL_SETTINGS: 'all-settings',

SEARCH: {
route: '/search/:query',
getRoute: (query: string) => `search/${query}` as const,
},

// This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated
CONCIERGE: 'concierge',
FLAG_COMMENT: {
Expand Down
4 changes: 4 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const SCREENS = {
UNLINK_LOGIN: 'UnlinkLogin',
SETTINGS_CENTRAL_PANE: 'SettingsCentralPane',
WORKSPACES_CENTRAL_PANE: 'WorkspacesCentralPane',
SEARCH: {
CENTRAL_PANE: 'Search_Central_Pane',
BOTTOM_TAB: 'Search_Bottom_Tab',
},
SETTINGS: {
ROOT: 'Settings_Root',
SHARE_CODE: 'Settings_Share_Code',
Expand Down
11 changes: 11 additions & 0 deletions src/components/TestToolMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as Network from '@userActions/Network';
import * as Session from '@userActions/Session';
import * as User from '@userActions/User';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Network as NetworkOnyx, User as UserOnyx} from '@src/types/onyx';
Expand Down Expand Up @@ -103,6 +104,16 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) {
}}
/>
</TestToolRow>
{/* Navigate to the new Search Page. This button is temporary and should be removed after passing QA tests. */}
<TestToolRow title="New Search Page">
<Button
small
text="Navigate"
onPress={() => {
Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL));
}}
/>
</TestToolRow>
</>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useActiveRoute.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {useContext} from 'react';
import ActiveRouteContext from '@libs/Navigation/AppNavigator/Navigators/ActiveRouteContext';
import type {CentralPaneNavigatorParamList, NavigationPartialRoute} from '@libs/Navigation/types';

function useActiveRoute(): string {
function useActiveRoute(): NavigationPartialRoute<keyof CentralPaneNavigatorParamList> | undefined {
return useContext(ActiveRouteContext);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import type {CentralPaneNavigatorParamList, NavigationPartialRoute} from '@libs/Navigation/types';

const ActiveRouteContext = React.createContext('');
const ActiveRouteContext = React.createContext<NavigationPartialRoute<keyof CentralPaneNavigatorParamList> | undefined>(undefined);

export default ActiveRouteContext;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import createCustomBottomTabNavigator from '@libs/Navigation/AppNavigator/create
import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute';
import type {BottomTabNavigatorParamList} from '@libs/Navigation/types';
import SidebarScreen from '@pages/home/sidebar/SidebarScreen';
import SearchPageBottomTab from '@pages/Search/SearchPageBottomTab';
import SCREENS from '@src/SCREENS';
import ActiveRouteContext from './ActiveRouteContext';

Expand All @@ -21,12 +22,16 @@ function BottomTabNavigator() {
const activeRoute = useNavigationState(getTopmostCentralPaneRoute);

return (
<ActiveRouteContext.Provider value={activeRoute?.name ?? ''}>
<ActiveRouteContext.Provider value={activeRoute}>
<Tab.Navigator screenOptions={screenOptions}>
<Tab.Screen
name={SCREENS.HOME}
component={SidebarScreen}
/>
<Tab.Screen
name={SCREENS.SEARCH.BOTTOM_TAB}
component={SearchPageBottomTab}
/>
<Tab.Screen
name={SCREENS.SETTINGS.ROOT}
getComponent={loadInitialSettingsPage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import ReportScreenWrapper from '@libs/Navigation/AppNavigator/ReportScreenWrapper';
import getCurrentUrl from '@libs/Navigation/currentUrl';
import type {CentralPaneNavigatorParamList} from '@navigation/types';
import SearchPage from '@pages/Search/SearchPage';
import SCREENS from '@src/SCREENS';

const Stack = createStackNavigator<CentralPaneNavigatorParamList>();
Expand Down Expand Up @@ -41,6 +42,12 @@ function BaseCentralPaneNavigator() {
initialParams={{openOnAdminRoom: openOnAdminRoom === 'true' || undefined}}
component={ReportScreenWrapper}
/>
<Stack.Screen
name={SCREENS.SEARCH.CENTRAL_PANE}
// We do it this way to avoid adding the url params to url
component={SearchPage}
/>

{Object.entries(settingsScreens).map(([screenName, componentGetter]) => (
<Stack.Screen
key={screenName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Session from '@libs/actions/Session';
import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute';
import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute';
import Navigation from '@libs/Navigation/Navigation';
import type {RootStackParamList, State} from '@libs/Navigation/types';
import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils';
Expand Down Expand Up @@ -64,6 +65,12 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps

// Parent navigator of the bottom tab bar is the root navigator.
const currentTabName = useNavigationState<RootStackParamList, string | undefined>((state) => {
const topmostCentralPaneRoute = getTopmostCentralPaneRoute(state);

if (topmostCentralPaneRoute && topmostCentralPaneRoute.name === SCREENS.SEARCH.CENTRAL_PANE) {
return SCREENS.SEARCH.CENTRAL_PANE;
}

const topmostBottomTabRoute = getTopmostBottomTabRoute(state);
return topmostBottomTabRoute?.name ?? SCREENS.HOME;
});
Expand Down Expand Up @@ -95,7 +102,6 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps
</View>
</PressableWithFeedback>
</Tooltip>

<BottomTabBarFloatingActionButton />
<BottomTabAvatar isSelected={currentTabName === SCREENS.SETTINGS.ROOT} />
</View>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@ import {createNavigatorFactory, useNavigationBuilder} from '@react-navigation/na
import type {StackNavigationEventMap, StackNavigationOptions} from '@react-navigation/stack';
import {StackView} from '@react-navigation/stack';
import React, {useEffect, useMemo} from 'react';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute';
import navigationRef from '@libs/Navigation/navigationRef';
import type {RootStackParamList, State} from '@libs/Navigation/types';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';
import CustomRouter from './CustomRouter';
import type {ResponsiveStackNavigatorProps, ResponsiveStackNavigatorRouterOptions} from './types';

type Routes = StackNavigationState<ParamListBase>['routes'];
function reduceReportRoutes(routes: Routes): Routes {
function reduceCentralPaneRoutes(routes: Routes): Routes {
const result: Routes = [];
let count = 0;
const reverseRoutes = [...routes].reverse();

reverseRoutes.forEach((route) => {
if (route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR) {
// Remove all report routes except the last 3. This will improve performance.
// Remove all central pane routes except the last 3. This will improve performance.
if (count < 3) {
result.push(route);
count++;
Expand All @@ -32,6 +37,7 @@ function reduceReportRoutes(routes: Routes): Routes {

function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) {
const {isSmallScreenWidth} = useWindowDimensions();
const styles = useThemeStyles();

const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder<
StackNavigationState<ParamListBase>,
Expand All @@ -52,15 +58,35 @@ function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) {
navigationRef.resetRoot(navigationRef.getRootState());
}, [isSmallScreenWidth]);

const stateToRender = useMemo(() => {
const result = reduceReportRoutes(state.routes);
const {stateToRender, searchRoute} = useMemo(() => {
const routes = reduceCentralPaneRoutes(state.routes);

const lastRoute = routes[routes.length - 1];
const isLastRouteSearchRoute = getTopmostCentralPaneRoute({routes: [lastRoute]} as State<RootStackParamList>)?.name === SCREENS.SEARCH.CENTRAL_PANE;

const firstRoute = routes[0];

// On narrow layout, if we are on /search route we want to hide all central pane routes and show only the bottom tab navigator.
if (isSmallScreenWidth && isLastRouteSearchRoute) {
return {
stateToRender: {
...state,
index: 0,
routes: [firstRoute],
},
searchRoute: lastRoute,
};
}

return {
...state,
index: result.length - 1,
routes: [...result],
stateToRender: {
...state,
index: routes.length - 1,
routes: [...routes],
},
searchRoute: undefined,
};
}, [state]);
}, [state, isSmallScreenWidth]);

return (
<NavigationContent>
Expand All @@ -71,6 +97,7 @@ function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) {
descriptors={descriptors}
navigation={navigation}
/>
{searchRoute && <View style={styles.dNone}>{descriptors[searchRoute.key].render()}</View>}
</NavigationContent>
);
}
Expand Down
13 changes: 1 addition & 12 deletions src/libs/Navigation/AppNavigator/getPartialStateDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,9 @@ import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRo
import getTopmostFullScreenRoute from '@libs/Navigation/getTopmostFullScreenRoute';
import type {Metainfo} from '@libs/Navigation/linkingConfig/getAdaptedStateFromPath';
import type {NavigationPartialRoute, RootStackParamList, State} from '@libs/Navigation/types';
import shallowCompare from '@libs/ObjectUtils';
import NAVIGATORS from '@src/NAVIGATORS';

// eslint-disable-next-line @typescript-eslint/ban-types
const shallowCompare = (obj1?: object, obj2?: object) => {
if (!obj1 && !obj2) {
return true;
}
if (obj1 && obj2) {
// @ts-expect-error we know that obj1 and obj2 are params of a route.
return Object.keys(obj1).length === Object.keys(obj2).length && Object.keys(obj1).every((key) => obj1[key] === obj2[key]);
}
return false;
};

type GetPartialStateDiffReturnType = {
[NAVIGATORS.BOTTOM_TAB_NAVIGATOR]?: NavigationPartialRoute;
[NAVIGATORS.CENTRAL_PANE_NAVIGATOR]?: NavigationPartialRoute;
Expand Down
11 changes: 5 additions & 6 deletions src/libs/Navigation/linkTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {getActionFromState} from '@react-navigation/core';
import type {NavigationAction, NavigationContainerRef, NavigationState, PartialState} from '@react-navigation/native';
import type {Writable} from 'type-fest';
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import shallowCompare from '@libs/ObjectUtils';
import {extractPolicyIDFromPath, getPathWithoutPolicyID} from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
Expand All @@ -13,7 +14,6 @@ import getPolicyIDFromState from './getPolicyIDFromState';
import getStateFromPath from './getStateFromPath';
import getTopmostBottomTabRoute from './getTopmostBottomTabRoute';
import getTopmostCentralPaneRoute from './getTopmostCentralPaneRoute';
import getTopmostReportId from './getTopmostReportId';
import linkingConfig from './linkingConfig';
import getAdaptedStateFromPath from './linkingConfig/getAdaptedStateFromPath';
import getMatchingBottomTabRouteForState from './linkingConfig/getMatchingBottomTabRouteForState';
Expand Down Expand Up @@ -152,16 +152,15 @@ export default function linkTo(navigation: NavigationContainerRef<RootStackParam
const topRouteName = rootState?.routes?.at(-1)?.name;
const isTargetNavigatorOnTop = topRouteName === action.payload.name;

const isTargetScreenDifferentThanCurrent = Boolean(topmostCentralPaneRoute && topmostCentralPaneRoute.name !== action.payload.params?.screen);
const areParamsDifferent = !shallowCompare(topmostCentralPaneRoute?.params, action.payload.params?.params);

// In case if type is 'FORCED_UP' we replace current screen with the provided. This means the current screen no longer exists in the stack
if (type === CONST.NAVIGATION.TYPE.FORCED_UP) {
action.type = CONST.NAVIGATION.ACTION_TYPE.REPLACE;

// If this action is navigating to the report screen and the top most navigator is different from the one we want to navigate - PUSH the new screen to the top of the stack
} else if (
action.payload.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR &&
topmostCentralPaneRoute &&
(topmostCentralPaneRoute.name !== action.payload.params?.screen || getTopmostReportId(rootState) !== getTopmostReportId(stateFromPath))
) {
} else if (action.payload.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR && (isTargetScreenDifferentThanCurrent || areParamsDifferent)) {
// We need to push a tab if the tab doesn't match the central pane route that we are going to push.
const topmostBottomTabRoute = getTopmostBottomTabRoute(rootState);
const matchingBottomTabRoute = getMatchingBottomTabRouteForState(stateFromPath, policyID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SCREENS from '@src/SCREENS';

const TAB_TO_CENTRAL_PANE_MAPPING: Record<BottomTabName, CentralPaneName[]> = {
[SCREENS.HOME]: [SCREENS.REPORT],
[SCREENS.SEARCH.BOTTOM_TAB]: [SCREENS.SEARCH.CENTRAL_PANE],
[SCREENS.SETTINGS.ROOT]: [
SCREENS.SETTINGS.PROFILE.ROOT,
SCREENS.SETTINGS.PREFERENCES.ROOT,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
exact: true,
},
[SCREENS.SETTINGS.WORKSPACES]: ROUTES.SETTINGS_WORKSPACES,
[SCREENS.SEARCH.CENTRAL_PANE]: ROUTES.SEARCH.route,
[SCREENS.SETTINGS.SAVE_THE_WORLD]: ROUTES.SETTINGS_SAVE_THE_WORLD,
},
},
Expand Down
4 changes: 3 additions & 1 deletion src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,9 @@ function getAdaptedState(state: PartialState<NavigationState<RootStackParamList>
// Routes
// - found bottom tab
// - matching central pane on desktop layout
if (isNarrowLayout) {

// We want to make sure that the bottom tab search page is always pushed with matching central pane page. Even on the narrow layout.
if (isNarrowLayout && bottomTabNavigator.state?.routes[0].name !== SCREENS.SEARCH.BOTTOM_TAB) {
return {
adaptedState: state,
metainfo,
Expand Down
4 changes: 4 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ type CentralPaneNavigatorParamList = {
[SCREENS.SETTINGS.ABOUT]: undefined;
[SCREENS.SETTINGS.TROUBLESHOOT]: undefined;
[SCREENS.SETTINGS.WORKSPACES]: undefined;
[SCREENS.SEARCH.CENTRAL_PANE]: {
query: string;
};
[SCREENS.SETTINGS.SAVE_THE_WORLD]: undefined;
};

Expand Down Expand Up @@ -724,6 +727,7 @@ type WelcomeVideoModalNavigatorParamList = {

type BottomTabNavigatorParamList = {
[SCREENS.HOME]: undefined;
[SCREENS.SEARCH.BOTTOM_TAB]: undefined;
[SCREENS.SETTINGS.ROOT]: undefined;
};

Expand Down
Loading

0 comments on commit 13a01fb

Please sign in to comment.