From a565567e0692fc698564ab2984a8718f58b4bc20 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 11 Jun 2024 14:21:31 +0200 Subject: [PATCH 1/7] Add multiple search bottom tabs --- src/libs/Navigation/linkTo/index.ts | 5 +++-- .../linkingConfig/getMatchingBottomTabRouteForState.ts | 6 +++++- src/libs/Navigation/types.ts | 8 +++++++- src/pages/Search/useCustomBackHandler/index.android.ts | 10 +--------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/libs/Navigation/linkTo/index.ts b/src/libs/Navigation/linkTo/index.ts index 313f525f390b..b1c5d072cb64 100644 --- a/src/libs/Navigation/linkTo/index.ts +++ b/src/libs/Navigation/linkTo/index.ts @@ -1,6 +1,6 @@ import {getActionFromState} from '@react-navigation/core'; -import {findFocusedRoute} from '@react-navigation/native'; import type {NavigationContainerRef, NavigationState, PartialState} from '@react-navigation/native'; +import {findFocusedRoute} from '@react-navigation/native'; import {omitBy} from 'lodash'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import extractPolicyIDsFromState from '@libs/Navigation/linkingConfig/extractPolicyIDsFromState'; @@ -86,9 +86,10 @@ export default function linkTo(navigation: NavigationContainerRef)?.policyID !== (matchingBottomTabRoute?.params as Record)?.policyID; - if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID)) { + if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID || matchingBottomTabRoute.name === SCREENS.SEARCH.BOTTOM_TAB)) { root.dispatch({ type: CONST.NAVIGATION.ACTION_TYPE.PUSH, payload: matchingBottomTabRoute, diff --git a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts index fd45685acf23..7fac455f2c5a 100644 --- a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts +++ b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts @@ -6,7 +6,7 @@ import {CENTRAL_PANE_TO_TAB_MAPPING} from './TAB_TO_CENTRAL_PANE_MAPPING'; // Get the route that matches the topmost central pane route in the navigation stack. e.g REPORT -> HOME function getMatchingBottomTabRouteForState(state: State, policyID?: string): NavigationPartialRoute { - const paramsWithPolicyID = policyID ? {policyID} : undefined; + let paramsWithPolicyID = policyID ? {policyID} : undefined; const defaultRoute = {name: SCREENS.HOME, params: paramsWithPolicyID}; const isFullScreenNavigatorOpened = state.routes.some((route) => route.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR); @@ -21,6 +21,10 @@ function getMatchingBottomTabRouteForState(state: State, pol } const tabName = CENTRAL_PANE_TO_TAB_MAPPING[topmostCentralPaneRoute.name]; + + if (tabName === SCREENS.SEARCH.BOTTOM_TAB) { + paramsWithPolicyID = {...topmostCentralPaneRoute.params, ...paramsWithPolicyID}; + } return {name: tabName, params: paramsWithPolicyID}; } diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 5597e7ce00da..e87f0380a2da 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -854,7 +854,13 @@ type WelcomeVideoModalNavigatorParamList = { type BottomTabNavigatorParamList = { [SCREENS.HOME]: {policyID?: string}; - [SCREENS.SEARCH.BOTTOM_TAB]: {policyID?: string}; + [SCREENS.SEARCH.BOTTOM_TAB]: { + query: string; + policyID?: string; + offset?: number; + sortBy?: SearchColumnType; + sortOrder?: SortOrder; + }; [SCREENS.SETTINGS.ROOT]: {policyID?: string}; }; diff --git a/src/pages/Search/useCustomBackHandler/index.android.ts b/src/pages/Search/useCustomBackHandler/index.android.ts index f168cb0b9008..ebf2525eef98 100644 --- a/src/pages/Search/useCustomBackHandler/index.android.ts +++ b/src/pages/Search/useCustomBackHandler/index.android.ts @@ -1,11 +1,8 @@ import {StackActions, useFocusEffect} from '@react-navigation/native'; import {useCallback} from 'react'; import {BackHandler} from 'react-native'; -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'; // We need to make sure that the central pane screen and bottom tab won't be desynchronized after using the physical back button on Android. // To achieve that we will call additional POP on the bottom tab navigator if the search page would disappear from the central pane. @@ -14,13 +11,8 @@ function useCustomBackHandler() { useCallback(() => { const onBackPress = () => { const rootState = navigationRef.getRootState(); - const bottomTabRoute = rootState.routes.find((route) => route.name === NAVIGATORS.BOTTOM_TAB_NAVIGATOR); - const centralPaneRouteAfterPop = getTopmostCentralPaneRoute({routes: [rootState.routes.at(-2)]} as State); - - if (bottomTabRoute && bottomTabRoute.state && (!centralPaneRouteAfterPop || centralPaneRouteAfterPop.name !== SCREENS.SEARCH.CENTRAL_PANE)) { - navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute.state.key}); - } + navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute?.state?.key}); return false; }; From b04f8f73db6d0fa46db33af200dd27fe24c19822 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 11 Jun 2024 14:38:20 +0200 Subject: [PATCH 2/7] Refactor getMatchingBottomTabRouteForState --- src/libs/Navigation/linkTo/index.ts | 4 +++- .../linkingConfig/getMatchingBottomTabRouteForState.ts | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/libs/Navigation/linkTo/index.ts b/src/libs/Navigation/linkTo/index.ts index b1c5d072cb64..bd304ec28abe 100644 --- a/src/libs/Navigation/linkTo/index.ts +++ b/src/libs/Navigation/linkTo/index.ts @@ -89,7 +89,9 @@ export default function linkTo(navigation: NavigationContainerRef)?.policyID !== (matchingBottomTabRoute?.params as Record)?.policyID; - if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID || matchingBottomTabRoute.name === SCREENS.SEARCH.BOTTOM_TAB)) { + const isOpeningSearch = matchingBottomTabRoute.name === SCREENS.SEARCH.BOTTOM_TAB; + + if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID || isOpeningSearch)) { root.dispatch({ type: CONST.NAVIGATION.ACTION_TYPE.PUSH, payload: matchingBottomTabRoute, diff --git a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts index 7fac455f2c5a..4b4ed25959f0 100644 --- a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts +++ b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts @@ -6,7 +6,7 @@ import {CENTRAL_PANE_TO_TAB_MAPPING} from './TAB_TO_CENTRAL_PANE_MAPPING'; // Get the route that matches the topmost central pane route in the navigation stack. e.g REPORT -> HOME function getMatchingBottomTabRouteForState(state: State, policyID?: string): NavigationPartialRoute { - let paramsWithPolicyID = policyID ? {policyID} : undefined; + const paramsWithPolicyID = policyID ? {policyID} : undefined; const defaultRoute = {name: SCREENS.HOME, params: paramsWithPolicyID}; const isFullScreenNavigatorOpened = state.routes.some((route) => route.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR); @@ -23,7 +23,12 @@ function getMatchingBottomTabRouteForState(state: State, pol const tabName = CENTRAL_PANE_TO_TAB_MAPPING[topmostCentralPaneRoute.name]; if (tabName === SCREENS.SEARCH.BOTTOM_TAB) { - paramsWithPolicyID = {...topmostCentralPaneRoute.params, ...paramsWithPolicyID}; + const topmostCentralPaneRouteParams = topmostCentralPaneRoute.params as Record; + delete topmostCentralPaneRouteParams?.policyIDs; + if (policyID) { + topmostCentralPaneRouteParams.policyID = policyID; + } + return {name: tabName, params: topmostCentralPaneRouteParams}; } return {name: tabName, params: paramsWithPolicyID}; } From e736a6f1eaf6a001c4ce481151bd29329ffcccb4 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 11 Jun 2024 18:29:47 +0200 Subject: [PATCH 3/7] fix linkTo and isNewPolicyID --- src/libs/Navigation/linkTo/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/Navigation/linkTo/index.ts b/src/libs/Navigation/linkTo/index.ts index bd304ec28abe..7d6d62b9a5aa 100644 --- a/src/libs/Navigation/linkTo/index.ts +++ b/src/libs/Navigation/linkTo/index.ts @@ -86,10 +86,10 @@ export default function linkTo(navigation: NavigationContainerRef)?.policyID !== (matchingBottomTabRoute?.params as Record)?.policyID; const isOpeningSearch = matchingBottomTabRoute.name === SCREENS.SEARCH.BOTTOM_TAB; + const isNewPolicyID = + ((topmostBottomTabRoute?.params as Record)?.policyID ?? '') !== + ((matchingBottomTabRoute?.params as Record)?.policyID ?? ''); if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID || isOpeningSearch)) { root.dispatch({ From 5fde996b5e54d73aa01c3bdb1ba9a077b0861d94 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 11 Jun 2024 18:31:08 +0200 Subject: [PATCH 4/7] remove old useCustomBackHandler --- src/pages/Search/SearchPage.tsx | 2 -- .../useCustomBackHandler/index.android.ts | 26 ------------------- .../Search/useCustomBackHandler/index.ts | 3 --- 3 files changed, 31 deletions(-) delete mode 100644 src/pages/Search/useCustomBackHandler/index.android.ts delete mode 100644 src/pages/Search/useCustomBackHandler/index.ts diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 8decc6477e21..4e80a4a0b619 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -14,7 +14,6 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {SearchQuery} from '@src/types/onyx/SearchResults'; import type IconAsset from '@src/types/utils/IconAsset'; -import useCustomBackHandler from './useCustomBackHandler'; type SearchPageProps = StackScreenProps; @@ -37,7 +36,6 @@ function SearchPage({route}: SearchPageProps) { const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); // We need to override default back button behavior on Android because we need to pop two screens, from the central pane and from the bottom tab. - useCustomBackHandler(); // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. diff --git a/src/pages/Search/useCustomBackHandler/index.android.ts b/src/pages/Search/useCustomBackHandler/index.android.ts deleted file mode 100644 index ebf2525eef98..000000000000 --- a/src/pages/Search/useCustomBackHandler/index.android.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {StackActions, useFocusEffect} from '@react-navigation/native'; -import {useCallback} from 'react'; -import {BackHandler} from 'react-native'; -import navigationRef from '@libs/Navigation/navigationRef'; -import NAVIGATORS from '@src/NAVIGATORS'; - -// We need to make sure that the central pane screen and bottom tab won't be desynchronized after using the physical back button on Android. -// To achieve that we will call additional POP on the bottom tab navigator if the search page would disappear from the central pane. -function useCustomBackHandler() { - useFocusEffect( - useCallback(() => { - const onBackPress = () => { - const rootState = navigationRef.getRootState(); - const bottomTabRoute = rootState.routes.find((route) => route.name === NAVIGATORS.BOTTOM_TAB_NAVIGATOR); - navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute?.state?.key}); - return false; - }; - - const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress); - - return () => subscription.remove(); - }, []), - ); -} - -export default useCustomBackHandler; diff --git a/src/pages/Search/useCustomBackHandler/index.ts b/src/pages/Search/useCustomBackHandler/index.ts deleted file mode 100644 index be753de818a6..000000000000 --- a/src/pages/Search/useCustomBackHandler/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -function useCustomBackHandler() {} - -export default useCustomBackHandler; From 67fed6a7f07f17a419921be58e744243e1975bd7 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 11 Jun 2024 18:32:38 +0200 Subject: [PATCH 5/7] add new back handler for android --- src/libs/Navigation/NavigationRoot.tsx | 3 + .../index.android.ts | 68 +++++++++++++++++++ .../setupCustomAndroidBackHandler/index.ts | 4 ++ 3 files changed, 75 insertions(+) create mode 100644 src/libs/Navigation/setupCustomAndroidBackHandler/index.android.ts create mode 100644 src/libs/Navigation/setupCustomAndroidBackHandler/index.ts diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 06a3dce8d59a..e4927c6d5f0d 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -18,6 +18,7 @@ import linkingConfig from './linkingConfig'; import customGetPathFromState from './linkingConfig/customGetPathFromState'; import getAdaptedStateFromPath from './linkingConfig/getAdaptedStateFromPath'; import Navigation, {navigationRef} from './Navigation'; +import setupCustomAndroidBackHandler from './setupCustomAndroidBackHandler'; import type {RootStackParamList} from './types'; type NavigationRootProps = { @@ -109,6 +110,8 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N useEffect(() => { if (firstRenderRef.current) { + setupCustomAndroidBackHandler(); + // we don't want to make the report back button go back to LHN if the user // started on the small screen so we don't set it on the first render // making it only work on consecutive changes of the screen size diff --git a/src/libs/Navigation/setupCustomAndroidBackHandler/index.android.ts b/src/libs/Navigation/setupCustomAndroidBackHandler/index.android.ts new file mode 100644 index 000000000000..ac272155289d --- /dev/null +++ b/src/libs/Navigation/setupCustomAndroidBackHandler/index.android.ts @@ -0,0 +1,68 @@ +import {findFocusedRoute, StackActions} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; +import {BackHandler} from 'react-native'; +import getTopmostCentralPaneRoute from '@navigation/getTopmostCentralPaneRoute'; +import navigationRef from '@navigation/navigationRef'; +import type {BottomTabNavigatorParamList, RootStackParamList, State} from '@navigation/types'; +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; + +type SearchPageProps = StackScreenProps; + +// We need to do some custom handling for the back button on Android for actions related to the search page. +function setupCustomAndroidBackHandler() { + const onBackPress = () => { + const rootState = navigationRef.getRootState(); + + const bottomTabRoute = rootState.routes.find((route) => route.name === NAVIGATORS.BOTTOM_TAB_NAVIGATOR); + const bottomTabRoutes = bottomTabRoute?.state?.routes; + const focusedRoute = findFocusedRoute(rootState); + + // Shoudn't happen but for type safety. + if (!bottomTabRoutes) { + return false; + } + + // Handle back press on the search page. + // We need to pop two screens, from the central pane and from the bottom tab. + if (bottomTabRoutes[bottomTabRoutes.length - 1].name === SCREENS.SEARCH.BOTTOM_TAB && focusedRoute?.name === SCREENS.SEARCH.CENTRAL_PANE) { + navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute?.state?.key}); + navigationRef.dispatch({...StackActions.pop()}); + + const centralPaneRouteAfterPop = getTopmostCentralPaneRoute({routes: [rootState.routes.at(-2)]} as State); + const bottomTabRouteAfterPop = bottomTabRoutes.at(-2); + + // It's possible that central pane search is desynchronized with the bottom tab search. + // e.g. opening a tab different than search will wipe out central pane screens. + // In that case we have to push the proper one. + if ( + bottomTabRouteAfterPop && + bottomTabRouteAfterPop.name === SCREENS.SEARCH.BOTTOM_TAB && + (!centralPaneRouteAfterPop || centralPaneRouteAfterPop.name !== SCREENS.SEARCH.CENTRAL_PANE) + ) { + const {policyID, ...restParams} = bottomTabRoutes[bottomTabRoutes.length - 2].params as SearchPageProps['route']['params']; + navigationRef.dispatch({...StackActions.push(NAVIGATORS.CENTRAL_PANE_NAVIGATOR, {screen: SCREENS.SEARCH.CENTRAL_PANE, params: {...restParams, policyIDs: policyID}})}); + } + + return true; + } + + // Handle back press to go back to the search page. + // It's possible that central pane search is desynchronized with the bottom tab search. + // e.g. opening a tab different than search will wipe out central pane screens. + // In that case we have to push the proper one. + if (bottomTabRoutes && bottomTabRoutes?.length >= 2 && bottomTabRoutes[bottomTabRoutes.length - 2].name === SCREENS.SEARCH.BOTTOM_TAB && rootState.routes.length === 1) { + const {policyID, ...restParams} = bottomTabRoutes[bottomTabRoutes.length - 2].params as SearchPageProps['route']['params']; + navigationRef.dispatch({...StackActions.push(NAVIGATORS.CENTRAL_PANE_NAVIGATOR, {screen: SCREENS.SEARCH.CENTRAL_PANE, params: {...restParams, policyIDs: policyID}})}); + navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute?.state?.key}); + return true; + } + + // Handle all other cases with default handler. + return false; + }; + + BackHandler.addEventListener('hardwareBackPress', onBackPress); +} + +export default setupCustomAndroidBackHandler; diff --git a/src/libs/Navigation/setupCustomAndroidBackHandler/index.ts b/src/libs/Navigation/setupCustomAndroidBackHandler/index.ts new file mode 100644 index 000000000000..aa9077e1220f --- /dev/null +++ b/src/libs/Navigation/setupCustomAndroidBackHandler/index.ts @@ -0,0 +1,4 @@ +// Do nothing for platforms different than Android. +function setupCustomAndroidBackHandler() {} + +export default setupCustomAndroidBackHandler; From b849a8ebe8b006bc1edad8f4bfdf27a144159edc Mon Sep 17 00:00:00 2001 From: Adam Grzybowski <67908363+adamgrzybowski@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:59:30 +0200 Subject: [PATCH 6/7] Update src/pages/Search/SearchPage.tsx Co-authored-by: Carlos Martins --- src/pages/Search/SearchPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 4e80a4a0b619..8f85c355c03c 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -35,7 +35,6 @@ function SearchPage({route}: SearchPageProps) { const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); - // We need to override default back button behavior on Android because we need to pop two screens, from the central pane and from the bottom tab. // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. From 149847bd94855bef3ba4bdabb652827f2af32f57 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 11 Jun 2024 19:03:21 +0200 Subject: [PATCH 7/7] remove empty line --- src/pages/Search/SearchPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 8f85c355c03c..f7b21592b090 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -35,7 +35,6 @@ function SearchPage({route}: SearchPageProps) { const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); - // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. if (isSmallScreenWidth) {