From 5448e843e02ccc35d5b3751a0bb4e1c2e4953f6f Mon Sep 17 00:00:00 2001 From: Vit Horacek <36083550+mountiny@users.noreply.github.com> Date: Fri, 21 Nov 2025 01:05:46 +0100 Subject: [PATCH] Revert "Create super wide RHP modal to display expense reports when accessed via Reports page or report previews" --- src/ROUTES.ts | 11 - src/SCREENS.ts | 4 - src/components/MoneyReportHeader.tsx | 18 +- .../MoneyRequestReportActionsList.tsx | 8 +- .../MoneyRequestReportTransactionItem.tsx | 7 +- .../MoneyRequestReportTransactionList.tsx | 4 +- .../MoneyRequestReportView.tsx | 112 ++--- src/components/ParentNavigationSubtitle.tsx | 35 +- .../MoneyRequestReportPreviewContent.tsx | 2 +- .../MoneyRequestReportPreview/index.tsx | 2 +- src/components/Search/index.tsx | 9 - .../WideRHPContextProvider/default.ts | 21 +- .../WideRHPContextProvider/index.native.tsx | 49 +- .../WideRHPContextProvider/index.tsx | 470 +++--------------- .../WideRHPContextProvider/types.ts | 49 +- .../Navigation/AppNavigator/AuthScreens.tsx | 11 +- .../ModalStackNavigators/index.tsx | 85 +--- .../useModalStackScreenOptions.ts | 10 +- .../Navigators/Overlay/BaseOverlay.tsx | 26 +- .../Navigators/RightModalNavigator.tsx | 131 +---- .../Navigators/SearchFullscreenNavigator.tsx | 5 + .../useRootNavigatorScreenOptions.ts | 35 +- src/libs/Navigation/Navigation.ts | 42 +- .../Navigation/helpers/isReportOpenInRHP.ts | 3 +- .../helpers/isReportOpenInSuperWideRHP.ts | 18 - .../linkingConfig/RELATIONS/SEARCH_TO_RHP.ts | 1 - src/libs/Navigation/linkingConfig/config.ts | 19 +- src/libs/Navigation/types.ts | 44 +- src/libs/actions/IOU.ts | 8 +- src/pages/Search/SearchHoldReasonPage.tsx | 6 +- .../Search/SearchMoneyRequestReportPage.tsx | 19 +- ...rchMoneyRequestReportVerifyAccountPage.tsx | 4 +- .../Search/SearchReportVerifyAccountPage.tsx | 4 +- .../TransactionMerge/ConfirmationPage.tsx | 2 +- src/pages/home/ReportScreen.tsx | 15 +- src/pages/iou/HoldReasonPage.tsx | 4 +- src/pages/iou/RejectReasonPage.tsx | 4 +- src/pages/iou/SplitExpensePage.tsx | 6 +- src/styles/index.ts | 32 +- src/styles/variables.ts | 1 - tests/actions/IOUTest.ts | 1 - tests/utils/TestNavigationContainer.tsx | 4 + 42 files changed, 252 insertions(+), 1089 deletions(-) delete mode 100644 src/libs/Navigation/helpers/isReportOpenInSuperWideRHP.ts diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2010bffe9869c..f69d235c0700f 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -83,17 +83,6 @@ const ROUTES = { return getUrlWithBackToParam(baseRoute, backTo); }, }, - - EXPENSE_REPORT_RHP: { - route: 'e/:reportID', - getRoute: ({reportID, backTo}: {reportID: string; backTo?: string}) => { - const baseRoute = `e/${reportID}` as const; - - // eslint-disable-next-line no-restricted-syntax -- Legacy route generation - return getUrlWithBackToParam(baseRoute, backTo); - }, - }, - SEARCH_REPORT_VERIFY_ACCOUNT: { route: `search/view/:reportID/${VERIFY_ACCOUNT}`, getRoute: (reportID: string) => `search/view/${reportID}/${VERIFY_ACCOUNT}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index dcc369f32e805..966881477b22f 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -244,8 +244,6 @@ const SCREENS = { TRANSACTION_DUPLICATE: 'TransactionDuplicate', TRAVEL: 'Travel', SEARCH_REPORT: 'SearchReport', - SEARCH_REPORT_ACTIONS: 'SearchReportActions', - SEARCH_MONEY_REQUEST_REPORT: 'SearchMoneyRequestReport', SEARCH_ADVANCED_FILTERS: 'SearchAdvancedFilters', SEARCH_SAVED_SEARCH: 'SearchSavedSearch', SETTINGS_CATEGORIES: 'SettingsCategories', @@ -261,10 +259,8 @@ const SCREENS = { REPORT_CHANGE_APPROVER: 'Report_Change_Approver', REPORT_VERIFY_ACCOUNT: 'Report_Verify_Account', MERGE_TRANSACTION: 'MergeTransaction', - EXPENSE_REPORT: 'ExpenseReport', DOMAIN: 'Domain', }, - EXPENSE_REPORT_RHP: 'Expense_Report_RHP', PUBLIC_CONSOLE_DEBUG: 'Console_Debug', SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 38b8ed3f1825a..5fd7d5693c54a 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -42,7 +42,7 @@ import Log from '@libs/Log'; import {getThreadReportIDsForTransactions, getTotalAmountForIOUReportPreviewButton} from '@libs/MoneyRequestReportUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {ReportsSplitNavigatorParamList, SearchMoneyRequestReportParamList, SearchReportParamList} from '@libs/Navigation/types'; +import type {ReportsSplitNavigatorParamList, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; import {buildOptimisticNextStepForPreventSelfApprovalsEnabled, buildOptimisticNextStepForStrictPolicyRuleViolations} from '@libs/NextStepUtils'; import type {KYCFlowEvent, TriggerKYCFlow} from '@libs/PaymentUtils'; import {selectPaymentType} from '@libs/PaymentUtils'; @@ -185,7 +185,7 @@ function MoneyReportHeader({ const shouldDisplayNarrowVersion = shouldUseNarrowLayout || isMediumScreenWidth; const route = useRoute< | PlatformStackRouteProp - | PlatformStackRouteProp + | PlatformStackRouteProp | PlatformStackRouteProp >(); const {login: currentUserLogin, accountID, email} = useCurrentUserPersonalDetails(); @@ -352,9 +352,9 @@ function MoneyReportHeader({ const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey, currentSearchHash} = useSearchContext(); const shouldCalculateTotals = useSearchShouldCalculateTotals(currentSearchKey, currentSearchQueryJSON?.similarSearchHash, true); - const {wideRHPRouteKeys, superWideRHPRouteKeys} = useContext(WideRHPContext); + const {wideRHPRouteKeys} = useContext(WideRHPContext); const [network] = useOnyx(ONYXKEYS.NETWORK, {canBeMissing: true}); - const shouldDisplayNarrowMoreButton = !shouldDisplayNarrowVersion || ((wideRHPRouteKeys.length > 0 || superWideRHPRouteKeys.length > 0) && !isSmallScreenWidth); + const shouldDisplayNarrowMoreButton = !shouldDisplayNarrowVersion || (wideRHPRouteKeys.length > 0 && !isSmallScreenWidth); const showExportProgressModal = useCallback(() => { return showConfirmModal({ @@ -1392,7 +1392,7 @@ function MoneyReportHeader({ const showNextStepBar = shouldShowNextStep && !!optimisticNextStep?.message?.length; const showNextStepSkeleton = shouldShowNextStep && !optimisticNextStep && !!isLoadingInitialReportActions && !isOffline; - const shouldShowMoreContent = showNextStepBar || showNextStepSkeleton || !!statusBarProps || (isReportInSearch && !shouldDisplayNarrowMoreButton); + const shouldShowMoreContent = showNextStepBar || showNextStepSkeleton || !!statusBarProps || isReportInSearch; return ( @@ -1433,12 +1433,6 @@ function MoneyReportHeader({ /> )} - {isReportInSearch && ( - - )} )} @@ -1483,7 +1477,7 @@ function MoneyReportHeader({ /> )} - {isReportInSearch && !shouldDisplayNarrowMoreButton && ( + {isReportInSearch && ( (''); diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index a89b62c7cd544..9e816cf999ec2 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -1,15 +1,11 @@ import {PortalHost} from '@gorhom/portal'; import React, {useCallback, useMemo} from 'react'; -// We use Animated for all functionality related to wide RHP to make it easier -// to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. -// eslint-disable-next-line no-restricted-imports -import {Animated, InteractionManager, ScrollView, View} from 'react-native'; +import {InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import HeaderGap from '@components/HeaderGap'; import MoneyReportHeader from '@components/MoneyReportHeader'; import MoneyRequestHeader from '@components/MoneyRequestHeader'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import MoneyRequestReceiptView from '@components/ReportActionItem/MoneyRequestReceiptView'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView'; import useNetwork from '@hooks/useNetwork'; @@ -17,7 +13,6 @@ import useNewTransactions from '@hooks/useNewTransactions'; import useOnyx from '@hooks/useOnyx'; import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import useParentReportAction from '@hooks/useParentReportAction'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport'; import {removeFailedReport} from '@libs/actions/Report'; @@ -61,16 +56,6 @@ function goBackFromSearchMoneyRequest() { const rootState = navigationRef.getRootState(); const lastRoute = rootState.routes.at(-1); - if (!lastRoute) { - Log.hmmm('[goBackFromSearchMoneyRequest()] No last route found in root state.'); - return; - } - - if (lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) { - Navigation.goBack(); - return; - } - if (lastRoute?.name !== NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR) { Log.hmmm('[goBackFromSearchMoneyRequest()] goBackFromSearchMoneyRequest was called from a different navigator than SearchFullscreenNavigator.'); return; @@ -99,10 +84,6 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const styles = useThemeStyles(); const {isOffline} = useNetwork(); - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth} = useResponsiveLayout(); - - const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false}); const reportID = report?.reportID; const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: true}); const [isComposerFullSize = false] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${reportID}`, {canBeMissing: true}); @@ -148,9 +129,6 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const isEmptyTransactionReport = visibleTransactions && visibleTransactions.length === 0 && transactionThreadReportID === undefined; const shouldDisplayMoneyRequestActionsList = !!isEmptyTransactionReport || shouldDisplayReportTableView(report, visibleTransactions ?? []); - const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, {canBeMissing: true}); - const shouldShowWideRHPReceipt = visibleTransactions.length === 1 && !isSmallScreenWidth && !!transactionThreadReport; - const reportHeaderView = useMemo( () => isTransactionThreadView ? ( @@ -233,61 +211,47 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe > {reportHeaderView} - - {shouldShowWideRHPReceipt && ( - - - - - + + {shouldDisplayMoneyRequestActionsList ? ( + + ) : ( + )} - - {shouldDisplayMoneyRequestActionsList ? ( - + - ) : ( - - )} - {shouldDisplayReportFooter ? ( - <> - - - - ) : null} - + + + ) : null} diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 9a0b348f02e1f..08379b5ed9188 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -12,7 +12,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName'; import Navigation from '@libs/Navigation/Navigation'; -import type {SearchMoneyRequestReportParamList} from '@libs/Navigation/types'; +import type {SearchFullscreenNavigatorParamList} from '@libs/Navigation/types'; import {getReportAction, shouldReportActionBeVisible} from '@libs/ReportActionsUtils'; import {canUserPerformWriteAction as canUserPerformWriteActionReportUtils} from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -86,20 +86,7 @@ function ParentNavigationSubtitle({ const isReportArchived = useReportIsArchived(report?.reportID); const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report, isReportArchived); const isReportInRHP = currentRoute.name === SCREENS.SEARCH.REPORT_RHP; - const {currentFullScreenRoute, currentFocusedNavigator} = useRootNavigationState((state) => { - const FullScreenRoute = state?.routes?.findLast((route) => isFullScreenName(route.name)); - - const FocusedNavigator = !state?.routes - ? undefined - : state.routes.findLast((route) => { - return route.state?.routes && route.state.routes.length > 0; - }); - - return { - currentFullScreenRoute: FullScreenRoute, - currentFocusedNavigator: FocusedNavigator, - }; - }); + const currentFullScreenRoute = useRootNavigationState((state) => state?.routes?.findLast((route) => isFullScreenName(route.name))); // We should not display the parent navigation subtitle if the user does not have access to the parent chat (the reportName is empty in this case) if (!reportName) { @@ -115,7 +102,7 @@ function ParentNavigationSubtitle({ if (currentFullScreenRoute?.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR) { const lastRoute = currentFullScreenRoute?.state?.routes.at(-1); if (lastRoute?.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT) { - const moneyRequestReportID = (lastRoute?.params as SearchMoneyRequestReportParamList[typeof SCREENS.SEARCH.MONEY_REQUEST_REPORT])?.reportID; + const moneyRequestReportID = (lastRoute?.params as SearchFullscreenNavigatorParamList[typeof SCREENS.SEARCH.MONEY_REQUEST_REPORT])?.reportID; // If the parent report is already displayed underneath RHP, simply dismiss the modal if (moneyRequestReportID === parentReportID) { Navigation.dismissModal(); @@ -123,22 +110,6 @@ function ParentNavigationSubtitle({ } } - // Dismiss wide RHP and go back to already opened super wide RHP if the parent report is already opened there - if (currentFocusedNavigator?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && currentFocusedNavigator.state?.index) { - const currentReportIndex = currentFocusedNavigator.state.index; - const previousRoute = currentFocusedNavigator.state.routes[currentReportIndex - 1]; - - if (previousRoute?.name === SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT) { - const lastPreviousRoute = previousRoute.state?.routes.at(-1); - const moneyRequestReportID = (lastPreviousRoute?.params as SearchMoneyRequestReportParamList[typeof SCREENS.SEARCH.MONEY_REQUEST_REPORT])?.reportID; - - if (moneyRequestReportID === parentReportID && lastPreviousRoute?.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT) { - Navigation.goBack(); - return; - } - } - } - Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID: parentReportID})); return; } diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx index 35732213926a9..26830e8db39bd 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx @@ -481,7 +481,7 @@ function MoneyRequestReportPreviewContent({ } Performance.markStart(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); Timing.start(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); - Navigation.navigate(ROUTES.EXPENSE_REPORT_RHP.getRoute({reportID: iouReportID, backTo: Navigation.getActiveRoute()})); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID, undefined, undefined, Navigation.getActiveRoute())); }, [iouReportID]); const reportPreviewAction = useMemo(() => { diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx index bb4a57719a708..d9cceea897a7f 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx @@ -106,7 +106,7 @@ function MoneyRequestReportPreview({ Performance.markStart(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); Timing.start(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); - Navigation.navigate(ROUTES.EXPENSE_REPORT_RHP.getRoute({reportID: iouReportID, backTo: Navigation.getActiveRoute()})); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID, undefined, undefined, Navigation.getActiveRoute())); }, [iouReportID]); const renderItem: ListRenderItem = ({item}) => ( diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 7dcb6f5387919..d5d83a8015c65 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -265,8 +265,6 @@ function Search({ const {accountID, email} = useCurrentUserPersonalDetails(); const [isActionLoadingSet = new Set()] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}`, {canBeMissing: true, selector: isActionLoadingSetSelector}); - const {markReportIDAsMultiTransactionExpense, unmarkReportIDAsMultiTransactionExpense} = useContext(WideRHPContext); - // Filter violations based on user visibility const filteredViolations = useMemo(() => { if (!violations || !searchResults?.data) { @@ -732,13 +730,6 @@ function Search({ setOptimisticDataForTransactionThreadPreview(firstTransaction, transactionPreviewData); } } - - if (item.transactions.length > 1) { - markReportIDAsMultiTransactionExpense(reportID); - } else { - unmarkReportIDAsMultiTransactionExpense(reportID); - } - requestAnimationFrame(() => Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID, backTo}))); return; } diff --git a/src/components/WideRHPContextProvider/default.ts b/src/components/WideRHPContextProvider/default.ts index a1ea9d6c38622..02c3a2d69c493 100644 --- a/src/components/WideRHPContextProvider/default.ts +++ b/src/components/WideRHPContextProvider/default.ts @@ -1,26 +1,19 @@ +// We use Animated for all functionality related to wide RHP to make it easier +// to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. +// eslint-disable-next-line no-restricted-imports +import {Animated} from 'react-native'; import type {WideRHPContextType} from './types'; const defaultWideRHPContextValue: WideRHPContextType = { wideRHPRouteKeys: [], + expandedRHPProgress: new Animated.Value(0), + secondOverlayProgress: new Animated.Value(0), shouldRenderSecondaryOverlay: false, showWideRHPVersion: () => {}, cleanWideRHPRouteKey: () => {}, markReportIDAsExpense: () => {}, - markReportIDAsMultiTransactionExpense: () => {}, - unmarkReportIDAsMultiTransactionExpense: () => {}, isReportIDMarkedAsExpense: () => false, - isReportIDMarkedAsMultiTransactionExpense: () => false, - dismissToFirstRHP: () => {}, - dismissToSecondRHP: () => {}, - isWideRHPClosing: false, - setIsWideRHPClosing: () => {}, - isWideRHPFocused: false, - shouldRenderTertiaryOverlay: false, - superWideRHPRouteKeys: [], - showSuperWideRHPVersion: () => {}, - syncWideRHPKeys: () => {}, - syncSuperWideRHPKeys: () => {}, - clearWideRHPKeys: () => {}, + dismissToWideReport: () => {}, }; export default defaultWideRHPContextValue; diff --git a/src/components/WideRHPContextProvider/index.native.tsx b/src/components/WideRHPContextProvider/index.native.tsx index dfeae5ad06a7d..96c955d933d71 100644 --- a/src/components/WideRHPContextProvider/index.native.tsx +++ b/src/components/WideRHPContextProvider/index.native.tsx @@ -3,29 +3,15 @@ import React, {createContext} from 'react'; // to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. // eslint-disable-next-line no-restricted-imports import {Animated} from 'react-native'; -import SCREENS from '@src/SCREENS'; import defaultWideRHPContextValue from './default'; import type {WideRHPContextType} from './types'; -const secondOverlayProgress = new Animated.Value(0); -const thirdOverlayProgress = new Animated.Value(0); - -const animatedReceiptPaneRHPWidth = new Animated.Value(0); -const animatedWideRHPWidth = new Animated.Value(0); -const animatedSuperWideRHPWidth = new Animated.Value(0); - -const modalStackOverlaySuperWideRHPWidth = new Animated.Value(0); -const modalStackOverlayWideRHPWidth = new Animated.Value(0); - const expandedRHPProgress = new Animated.Value(0); -const innerRHPProgress = new Animated.Value(0); +const secondOverlayProgress = new Animated.Value(0); +const receiptPaneRHPWidth = new Animated.Value(0); const WideRHPContext = createContext(defaultWideRHPContextValue); -const WIDE_RIGHT_MODALS = new Set([SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT, SCREENS.RIGHT_MODAL.EXPENSE_REPORT, SCREENS.RIGHT_MODAL.SEARCH_REPORT]); - -const SUPER_WIDE_RIGHT_MODALS = new Set([SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT, SCREENS.RIGHT_MODAL.EXPENSE_REPORT]); - function WideRHPContextProvider({children}: React.PropsWithChildren) { return {children}; } @@ -34,37 +20,8 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { // eslint-disable-next-line @typescript-eslint/no-unused-vars function useShowWideRHPVersion(condition: boolean) {} -// Super Wide RHP is not displayed on native platforms -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function useShowSuperWideRHPVersion(condition: boolean) {} - -// Wide RHP is not displayed on native platforms -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const calculateReceiptPaneRHPWidth = (windowWidth: number) => {}; - -// Super Wide RHP is not displayed on native platforms -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const calculateSuperWideRHPWidth = (windowWidth: number) => {}; - WideRHPContextProvider.displayName = 'WideRHPContextProvider'; export default WideRHPContextProvider; export type {WideRHPContextType}; -export { - calculateReceiptPaneRHPWidth, - calculateSuperWideRHPWidth, - animatedSuperWideRHPWidth, - modalStackOverlaySuperWideRHPWidth, - modalStackOverlayWideRHPWidth, - animatedReceiptPaneRHPWidth, - secondOverlayProgress, - thirdOverlayProgress, - useShowSuperWideRHPVersion, - useShowWideRHPVersion, - WideRHPContext, - animatedWideRHPWidth, - innerRHPProgress, - expandedRHPProgress, - WIDE_RIGHT_MODALS, - SUPER_WIDE_RIGHT_MODALS, -}; +export {expandedRHPProgress, secondOverlayProgress, WideRHPContext, useShowWideRHPVersion, receiptPaneRHPWidth}; diff --git a/src/components/WideRHPContextProvider/index.tsx b/src/components/WideRHPContextProvider/index.tsx index 9fa01116f958f..a0fe2c3b7ef55 100644 --- a/src/components/WideRHPContextProvider/index.tsx +++ b/src/components/WideRHPContextProvider/index.tsx @@ -1,5 +1,5 @@ -import type {NavigationState, PartialState} from '@react-navigation/native'; import {findFocusedRoute, StackActions, useNavigation, useRoute} from '@react-navigation/native'; +import type {NavigationState, PartialState} from '@react-navigation/native'; import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; // We use Animated for all functionality related to wide RHP to make it easier // to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. @@ -22,20 +22,9 @@ import type {WideRHPContextType} from './types'; // 0 is folded/hidden, 1 is expanded/shown const expandedRHPProgress = new Animated.Value(0); -const innerRHPProgress = new Animated.Value(0); const secondOverlayProgress = new Animated.Value(0); -const thirdOverlayProgress = new Animated.Value(0); - -const OVERLAY_TIMING_DURATION = 300; -// This array includes wide and super wide right modals -const WIDE_RIGHT_MODALS = new Set([SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT, SCREENS.RIGHT_MODAL.EXPENSE_REPORT, SCREENS.RIGHT_MODAL.SEARCH_REPORT]); - -const SUPER_WIDE_RIGHT_MODALS = new Set([SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT, SCREENS.RIGHT_MODAL.EXPENSE_REPORT]); - -function isSuperWideRHPRouteName(routeName: string) { - return routeName === SCREENS.SEARCH.MONEY_REQUEST_REPORT || routeName === SCREENS.EXPENSE_REPORT_RHP; -} +const wideRHPMaxWidth = variables.receiptPaneRHPMaxWidth + variables.sideBarWidth; /** * Utility function that extracts all unique navigation keys from a React Navigation state. @@ -72,9 +61,6 @@ function extractNavigationKeys(state: NavigationState | PartialState { const calculatedWidth = windowWidth < wideRHPMaxWidth ? variables.receiptPaneRHPMaxWidth - (wideRHPMaxWidth - windowWidth) : variables.receiptPaneRHPMaxWidth; - return Math.max(calculatedWidth, variables.mobileResponsiveWidthBreakpoint - singleRHPWidth); + return Math.max(calculatedWidth, variables.mobileResponsiveWidthBreakpoint - variables.sideBarWidth); }; -/** - * Calculates the optimal width for the super wide RHP based on window width. - * Ensures the RHP doesn't exceed maximum width and maintains minimum responsive width. - * - * @param windowWidth - Current window width in pixels - * @returns Calculated super wide RHP width with constraints applied - */ -const calculateSuperWideRHPWidth = (windowWidth: number) => { - const superWideRHPWidth = windowWidth - variables.navigationTabBarSize - variables.sideBarWithLHBWidth; - const wideRHPWidth = calculateReceiptPaneRHPWidth(windowWidth) + variables.sideBarWidth; - - return Math.max(Math.min(superWideRHPWidth, variables.superWideRHPMaxWidth), wideRHPWidth); -}; - -const receiptPaneRHPWidth = calculateReceiptPaneRHPWidth(Dimensions.get('window').width); -const superWideRHPWidth = calculateReceiptPaneRHPWidth(Dimensions.get('window').width); -const wideRHPWidth = receiptPaneRHPWidth + singleRHPWidth; - -// This animated value is necessary to have responsive RHP widths -const animatedReceiptPaneRHPWidth = new Animated.Value(receiptPaneRHPWidth); -const animatedSuperWideRHPWidth = new Animated.Value(superWideRHPWidth); -const animatedWideRHPWidth = new Animated.Value(wideRHPWidth); -const modalStackOverlayWideRHPWidth = new Animated.Value(superWideRHPWidth - wideRHPWidth); -const modalStackOverlaySuperWideRHPWidth = new Animated.Value(superWideRHPWidth - singleRHPWidth); +// This animated value is necessary to have a responsive RHP width for the range 800px to 840px. +const receiptPaneRHPWidth = new Animated.Value(calculateReceiptPaneRHPWidth(Dimensions.get('window').width)); const WideRHPContext = createContext(defaultWideRHPContextValue); @@ -127,189 +91,77 @@ const expenseReportSelector = (reports: OnyxCollection) => { ); }; -function getCurrentWideRHPKeys(allWideRHPKeys: string[], lastVisibleRHPRouteKey: string | undefined) { - const rootState = navigationRef.getRootState(); - - if (!rootState) { - return []; - } - - const lastRHPRoute = rootState.routes.find((route) => route.key === lastVisibleRHPRouteKey); - - if (!lastRHPRoute) { - return []; - } - - const lastRHPKeys = extractNavigationKeys(lastRHPRoute.state); - const currentKeys = allWideRHPKeys.filter((key) => lastRHPKeys.has(key)); - - return currentKeys; -} - -function getLastVisibleRHPRouteKey(state: NavigationState | undefined) { - // Safe handling when navigation is not yet initialized - if (!state) { - return undefined; - } - const lastFullScreenRouteIndex = state?.routes.findLastIndex((route) => isFullScreenName(route.name)); - const lastRHPRouteIndex = state?.routes.findLastIndex((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR); - - // Both routes have to be present and the RHP have to be after last full screen for it to be visible. - if (lastFullScreenRouteIndex === -1 || lastRHPRouteIndex === -1 || lastFullScreenRouteIndex > lastRHPRouteIndex) { - return undefined; - } - - return state?.routes.at(lastRHPRouteIndex)?.key; -} - function WideRHPContextProvider({children}: React.PropsWithChildren) { // We have a separate containers for allWideRHPRouteKeys and wideRHPRouteKeys because we may have two or more RHPs on the stack. // For convenience and proper overlay logic wideRHPRouteKeys will show only the keys existing in the last RHP. const [allWideRHPRouteKeys, setAllWideRHPRouteKeys] = useState([]); - const [wideRHPRouteKeys, setWideRHPRouteKeys] = useState([]); - const [allSuperWideRHPRouteKeys, setAllSuperWideRHPRouteKeys] = useState([]); - const [superWideRHPRouteKeys, setSuperWideRHPRouteKeys] = useState([]); const [shouldRenderSecondaryOverlay, setShouldRenderSecondaryOverlay] = useState(false); - const [shouldRenderTertiaryOverlay, setShouldRenderTertiaryOverlay] = useState(false); const [expenseReportIDs, setExpenseReportIDs] = useState>(new Set()); - const [multiTransactionExpenseReportIDs, setMultiTransactionExpenseReportIDs] = useState>(new Set()); - const [isWideRHPClosing, setIsWideRHPClosing] = useState(false); - const focusedRouteKey = useRootNavigationState((state) => (state ? findFocusedRoute(state)?.key : undefined)); const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: expenseReportSelector, canBeMissing: true}); - const syncWideRHPKeys = useCallback(() => { - setWideRHPRouteKeys(getCurrentWideRHPKeys(allWideRHPRouteKeys, getLastVisibleRHPRouteKey(navigationRef.getRootState()))); - }, [allWideRHPRouteKeys]); - - const syncSuperWideRHPKeys = useCallback(() => { - setSuperWideRHPRouteKeys(getCurrentWideRHPKeys(allSuperWideRHPRouteKeys, getLastVisibleRHPRouteKey(navigationRef.getRootState()))); - }, [allSuperWideRHPRouteKeys]); - - const clearWideRHPKeys = useCallback(() => { - setWideRHPRouteKeys([]); - setSuperWideRHPRouteKeys([]); - }, []); - - useEffect(() => { - syncWideRHPKeys(); - }, [allWideRHPRouteKeys, syncWideRHPKeys]); - - useEffect(() => { - syncSuperWideRHPKeys(); - }, [allSuperWideRHPRouteKeys, syncSuperWideRHPKeys]); - - const isWideRHPFocused = useMemo(() => { - return !!focusedRouteKey && wideRHPRouteKeys.includes(focusedRouteKey); - }, [focusedRouteKey, wideRHPRouteKeys]); - - const getIsSuperWideRHPBelowFocusedScreen = useCallback( - (state: NavigationState | undefined, lastVisibleRouteKey: string | undefined) => { - if (!state) { - return false; - } - - const focusedRoute = findFocusedRoute(state); + // Return undefined if RHP is not the last route + const lastVisibleRHPRouteKey = useRootNavigationState((state) => { + // Safe handling when navigation is not yet initialized + if (!state) { + return undefined; + } + const lastFullScreenRouteIndex = state?.routes.findLastIndex((route) => isFullScreenName(route.name)); + const lastRHPRouteIndex = state?.routes.findLastIndex((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR); - // Shouldn't ever happen but for type safety - if (!focusedRoute?.key) { - return false; - } + // Both routes have to be present and the RHP have to be after last full screen for it to be visible. + if (lastFullScreenRouteIndex === -1 || lastRHPRouteIndex === -1 || lastFullScreenRouteIndex > lastRHPRouteIndex) { + return undefined; + } - const currentSuperWideRHPRouteKeys = getCurrentWideRHPKeys(allSuperWideRHPRouteKeys, lastVisibleRouteKey); - const isFocusedRouteSuperWide = isSuperWideRHPRouteName(focusedRoute.name); + return state?.routes.at(lastRHPRouteIndex)?.key; + }); - return currentSuperWideRHPRouteKeys.length > 0 && !currentSuperWideRHPRouteKeys.includes(focusedRoute.key) && !isFocusedRouteSuperWide; - }, - [allSuperWideRHPRouteKeys], - ); + const wideRHPRouteKeys = useMemo(() => { + const rootState = navigationRef.getRootState(); - const getIsWideRHPBelowFocusedScreen = useCallback( - (state: NavigationState | undefined, lastVisibleRouteKey: string | undefined) => { - if (!state) { - return false; - } + if (!rootState) { + return []; + } - const focusedRoute = findFocusedRoute(state); + const lastRHPRoute = rootState.routes.find((route) => route.key === lastVisibleRHPRouteKey); - // Shouldn't ever happen but for type safety - if (!focusedRoute?.key) { - return false; - } + if (!lastRHPRoute) { + return []; + } - const currentWideRHPRouteKeys = getCurrentWideRHPKeys(allWideRHPRouteKeys, lastVisibleRouteKey); - const isFocusedRouteWide = focusedRoute.name === SCREENS.SEARCH.REPORT_RHP; + const lastRHPKeys = extractNavigationKeys(lastRHPRoute.state); + const currentKeys = allWideRHPRouteKeys.filter((key) => lastRHPKeys.has(key)); - return currentWideRHPRouteKeys.length > 0 && !currentWideRHPRouteKeys.includes(focusedRoute.key) && !isFocusedRouteWide; - }, - [allWideRHPRouteKeys], - ); + return currentKeys; + }, [allWideRHPRouteKeys, lastVisibleRHPRouteKey]); /** * Determines whether the secondary overlay should be displayed. + * Shows second overlay when RHP is open and there is a wide RHP route open but there is another regular route on the top. */ const shouldShowSecondaryOverlay = useRootNavigationState((state) => { - const isRHPLastRootRoute = state?.routes.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - - if (!isRHPLastRootRoute || !state) { + // Safe handling when navigation is not yet initialized + if (!state) { return false; } const focusedRoute = findFocusedRoute(state); - - // Shouldn't ever happen but for type safety - if (!focusedRoute?.key) { - return false; - } - - // For this screen the secondary overlay should never be shown, it is always displayed as 1 RHP in the order - if (isSuperWideRHPRouteName(focusedRoute.name)) { - return false; - } - - const currentLastVisibleRHPRouteKey = getLastVisibleRHPRouteKey(state); - const isWideRHPBelow = getIsWideRHPBelowFocusedScreen(state, currentLastVisibleRHPRouteKey); - - if (isWideRHPBelow) { - return true; - } - - const isSuperWideRHPBelow = getIsSuperWideRHPBelowFocusedScreen(state, currentLastVisibleRHPRouteKey); - return isSuperWideRHPBelow; - }); - - /** - * Determines whether the tertiary overlay should be displayed. - */ - const shouldShowTertiaryOverlay = useRootNavigationState((state) => { const isRHPLastRootRoute = state?.routes.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - if (!isRHPLastRootRoute) { - return false; - } - - const rhpState = state?.routes?.at(-1)?.state; - // Shouldn't ever happen but for type safety - if (!rhpState?.key) { - return false; - } - - const lastSuperWideRHPIndex = rhpState?.routes?.findLastIndex((route) => SUPER_WIDE_RIGHT_MODALS.has(route.name)) ?? -1; - const isSuperWideRHPDirectlyBelowFocusedScreen = lastSuperWideRHPIndex === rhpState.routes.length - 2; - - if (isSuperWideRHPDirectlyBelowFocusedScreen) { + if (!focusedRoute?.key) { return false; } - const currentLastVisibleRHPRouteKey = getLastVisibleRHPRouteKey(state); - const isWideRHPBelow = getIsWideRHPBelowFocusedScreen(state, currentLastVisibleRHPRouteKey); - - if (!isWideRHPBelow) { - return false; + // We want to show the second overlay only if + // 1. The currently focused route is not the wide report RHP + // 2. A wide report RHP exists on the navigation stack + if (isRHPLastRootRoute && focusedRoute.name !== SCREENS.SEARCH.REPORT_RHP) { + const searchReportRightModalRoute = state?.routes.at(-1)?.state?.routes.find((route) => route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT); + return !!searchReportRightModalRoute && searchReportRightModalRoute.state?.routes.find((route) => route.name === SCREENS.SEARCH.REPORT_RHP); } - const isSuperWideRHPBelow = getIsSuperWideRHPBelowFocusedScreen(state, currentLastVisibleRHPRouteKey); - return isSuperWideRHPBelow; + return false; }); /** @@ -324,24 +176,9 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { const newKey = route.key; // If the key is in the array, don't add it. - // Ensure mutual exclusivity: remove key from other array if present - setAllSuperWideRHPRouteKeys((prev) => (prev.includes(newKey) ? prev.filter((key) => key !== newKey) : prev)); setAllWideRHPRouteKeys((prev) => (prev.includes(newKey) ? prev : [newKey, ...prev])); }, []); - const showSuperWideRHPVersion = useCallback((route: NavigationRoute) => { - if (!route.key) { - console.error(`The route passed to showWideRHPVersion should have the "key" property defined.`); - return; - } - - const newKey = route.key; - // If the key is in the array, don't add it. - // Ensure mutual exclusivity: remove key from other array if present - setAllWideRHPRouteKeys((prev) => (prev.includes(newKey) ? prev.filter((key) => key !== newKey) : prev)); - setAllSuperWideRHPRouteKeys((prev) => (prev.includes(newKey) ? prev : [newKey, ...prev])); - }, []); - /** * Removes a route from the wide RHP route keys list, disabling wide RHP display for that route. */ @@ -355,51 +192,19 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { const keyToRemove = route.key; // Do nothing, the key is not here - if (!allWideRHPRouteKeys.includes(keyToRemove) && !superWideRHPRouteKeys.includes(keyToRemove)) { + if (!allWideRHPRouteKeys.includes(keyToRemove)) { return; } setAllWideRHPRouteKeys((prev) => (prev.includes(keyToRemove) ? prev.filter((key) => key !== keyToRemove) : prev)); - setAllSuperWideRHPRouteKeys((prev) => (prev.includes(keyToRemove) ? prev.filter((key) => key !== keyToRemove) : prev)); }, - [allWideRHPRouteKeys, superWideRHPRouteKeys], + [allWideRHPRouteKeys], ); /** * Dismiss top layer modal and go back to the wide RHP. */ - const dismissToFirstRHP = useCallback(() => { - const rootState = navigationRef.getRootState(); - if (!rootState) { - return; - } - - const rhpState = rootState.routes.findLast((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR)?.state; - - if (!rhpState) { - return; - } - - let firstRHPIndex; - - // If Wide RHP is focused, we should dismiss to Super Wide RHP - if (isWideRHPFocused) { - firstRHPIndex = rhpState.routes.findLastIndex((route) => SUPER_WIDE_RIGHT_MODALS.has(route.name)); - } else { - firstRHPIndex = rhpState.routes.findLastIndex((route) => WIDE_RIGHT_MODALS.has(route.name)); - } - - const routesToPop = rhpState.routes.length - firstRHPIndex - 1; - - // In the current navigation structure, hardcoding popTo SCREENS.RIGHT_MODAL.SEARCH_REPORT works exactly as we want. - // It may change in the future and we may need to improve this function to handle more complex configurations. - navigationRef.dispatch({...StackActions.pop(routesToPop), target: rhpState.key}); - }, [isWideRHPFocused]); - - /** - * Dismiss top layer modal and go back to the wide RHP. - */ - const dismissToSecondRHP = useCallback(() => { + const dismissToWideReport = useCallback(() => { const rootState = navigationRef.getRootState(); if (!rootState) { return; @@ -450,62 +255,16 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { [expenseReportIDs], ); - /** - * Marks a report ID as a multi-transaction expense report, adding it to the expense reports set. - * This enables optimistic super wide RHP display for expense reports. - * It helps us open expense as super wide, before it fully loads. - */ - const markReportIDAsMultiTransactionExpense = useCallback((reportID: string) => { - setMultiTransactionExpenseReportIDs((prev) => { - const newSet = new Set(prev); - newSet.add(reportID); - return newSet; - }); - }, []); - - /** - * Removes a report ID from the multi-transaction expense reports set. - * This disables optimistic super wide RHP display for that specific report - * (e.g., when transactions are deleted or report no longer qualifies as multi-transaction) - */ - const unmarkReportIDAsMultiTransactionExpense = useCallback((reportID: string) => { - setMultiTransactionExpenseReportIDs((prev) => { - const newSet = new Set(prev); - newSet.delete(reportID); - return newSet; - }); - }, []); - - /** - * Checks if a report ID is marked as a multi-transaction expense report. - * Used to determine if super wide RHP should be displayed optimistically. - * It helps us open expense as super wide, before it fully loads. - */ - const isReportIDMarkedAsMultiTransactionExpense = useCallback( - (reportID: string) => { - return multiTransactionExpenseReportIDs.has(reportID); - }, - [multiTransactionExpenseReportIDs], - ); - /** * Effect that shows/hides the expanded RHP progress based on the number of wide RHP routes. */ useEffect(() => { - const numberOfSuperWideRoutes = superWideRHPRouteKeys.length; - const numberOfWideRoutes = wideRHPRouteKeys.length; - - if (numberOfSuperWideRoutes > 0) { - expandedRHPProgress.setValue(2); - innerRHPProgress.setValue(numberOfWideRoutes > 0 ? 1 : 0); - } else if (numberOfWideRoutes > 0) { + if (wideRHPRouteKeys.length > 0) { expandedRHPProgress.setValue(1); - innerRHPProgress.setValue(0); } else { expandedRHPProgress.setValue(0); - innerRHPProgress.setValue(0); } - }, [superWideRHPRouteKeys.length, wideRHPRouteKeys.length]); + }, [wideRHPRouteKeys.length]); /** * Effect that manages the secondary overlay animation and rendering state. @@ -515,13 +274,13 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { setShouldRenderSecondaryOverlay(true); Animated.timing(secondOverlayProgress, { toValue: 1, - duration: OVERLAY_TIMING_DURATION, + duration: 300, useNativeDriver: false, }).start(); } else { Animated.timing(secondOverlayProgress, { toValue: 0, - duration: OVERLAY_TIMING_DURATION, + duration: 300, useNativeDriver: false, }).start(() => { setShouldRenderSecondaryOverlay(false); @@ -529,28 +288,6 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { } }, [shouldShowSecondaryOverlay]); - /** - * Effect that manages the secondary overlay animation and rendering state. - */ - useEffect(() => { - if (shouldShowTertiaryOverlay) { - setShouldRenderTertiaryOverlay(true); - Animated.timing(thirdOverlayProgress, { - toValue: 1, - duration: OVERLAY_TIMING_DURATION, - useNativeDriver: false, - }).start(); - } else { - Animated.timing(thirdOverlayProgress, { - toValue: 0, - duration: OVERLAY_TIMING_DURATION, - useNativeDriver: false, - }).start(() => { - setShouldRenderTertiaryOverlay(false); - }); - } - }, [shouldShowTertiaryOverlay]); - /** * Effect that handles responsive RHP width calculation when window dimensions change. * Listens for dimension changes and recalculates the optimal RHP width accordingly. @@ -558,14 +295,9 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { useEffect(() => { const handleDimensionChange = () => { const windowWidth = Dimensions.get('window').width; - const newReceiptPaneRHPWidth = calculateReceiptPaneRHPWidth(windowWidth); - const newSuperWideRHPWidth = calculateSuperWideRHPWidth(windowWidth); - const newWideRHPWidth = newReceiptPaneRHPWidth + singleRHPWidth; - animatedReceiptPaneRHPWidth.setValue(newReceiptPaneRHPWidth); - animatedWideRHPWidth.setValue(newWideRHPWidth); - modalStackOverlayWideRHPWidth.setValue(newSuperWideRHPWidth - newWideRHPWidth); - modalStackOverlaySuperWideRHPWidth.setValue(newSuperWideRHPWidth - singleRHPWidth); - animatedSuperWideRHPWidth.setValue(newSuperWideRHPWidth); + const newWidth = calculateReceiptPaneRHPWidth(windowWidth); + + receiptPaneRHPWidth.setValue(newWidth); }; // Set initial value @@ -582,47 +314,15 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { () => ({ expandedRHPProgress, wideRHPRouteKeys, - superWideRHPRouteKeys, showWideRHPVersion, - showSuperWideRHPVersion, cleanWideRHPRouteKey, + secondOverlayProgress, shouldRenderSecondaryOverlay, - shouldRenderTertiaryOverlay, - dismissToFirstRHP, - dismissToSecondRHP, + dismissToWideReport, markReportIDAsExpense, - markReportIDAsMultiTransactionExpense, - unmarkReportIDAsMultiTransactionExpense, isReportIDMarkedAsExpense, - isReportIDMarkedAsMultiTransactionExpense, - isWideRHPFocused, - isWideRHPClosing, - setIsWideRHPClosing, - syncWideRHPKeys, - syncSuperWideRHPKeys, - clearWideRHPKeys, }), - [ - wideRHPRouteKeys, - superWideRHPRouteKeys, - showWideRHPVersion, - showSuperWideRHPVersion, - cleanWideRHPRouteKey, - shouldRenderSecondaryOverlay, - shouldRenderTertiaryOverlay, - dismissToFirstRHP, - dismissToSecondRHP, - markReportIDAsExpense, - markReportIDAsMultiTransactionExpense, - unmarkReportIDAsMultiTransactionExpense, - isReportIDMarkedAsExpense, - isReportIDMarkedAsMultiTransactionExpense, - isWideRHPFocused, - isWideRHPClosing, - syncWideRHPKeys, - syncSuperWideRHPKeys, - clearWideRHPKeys, - ], + [wideRHPRouteKeys, showWideRHPVersion, cleanWideRHPRouteKey, shouldRenderSecondaryOverlay, dismissToWideReport, markReportIDAsExpense, isReportIDMarkedAsExpense], ); return {children}; @@ -639,7 +339,7 @@ function useShowWideRHPVersion(condition: boolean) { const navigation = useNavigation(); const route = useRoute(); const reportID = route.params && 'reportID' in route.params && typeof route.params.reportID === 'string' ? route.params.reportID : ''; - const {showWideRHPVersion, cleanWideRHPRouteKey, isReportIDMarkedAsExpense, setIsWideRHPClosing} = useContext(WideRHPContext); + const {showWideRHPVersion, cleanWideRHPRouteKey, isReportIDMarkedAsExpense} = useContext(WideRHPContext); /** * Effect that sets up cleanup when the screen is about to be removed. @@ -647,14 +347,12 @@ function useShowWideRHPVersion(condition: boolean) { */ useEffect(() => { return navigation.addListener('beforeRemove', () => { - setIsWideRHPClosing(true); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { cleanWideRHPRouteKey(route); - setIsWideRHPClosing(false); }); }); - }, [cleanWideRHPRouteKey, navigation, route, setIsWideRHPClosing]); + }, [cleanWideRHPRouteKey, navigation, route]); /** * Effect that determines whether to show wide RHP based on condition or optimistic state. @@ -671,58 +369,8 @@ function useShowWideRHPVersion(condition: boolean) { }, [condition, reportID, isReportIDMarkedAsExpense, route, showWideRHPVersion]); } -function useShowSuperWideRHPVersion(condition: boolean) { - const navigation = useNavigation(); - const route = useRoute(); - const reportID = route.params && 'reportID' in route.params && typeof route.params.reportID === 'string' ? route.params.reportID : ''; - const {showWideRHPVersion, showSuperWideRHPVersion, cleanWideRHPRouteKey, isReportIDMarkedAsExpense, isReportIDMarkedAsMultiTransactionExpense} = useContext(WideRHPContext); - - /** - * Effect that sets up cleanup when the screen is about to be removed. - * Uses InteractionManager to ensure cleanup happens after closing animation. - */ - useEffect(() => { - return navigation.addListener('beforeRemove', () => { - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => { - cleanWideRHPRouteKey(route); - }); - }); - }, [cleanWideRHPRouteKey, navigation, route]); - - /** - * Effect that determines whether to show wide RHP based on condition or optimistic state. - * Shows wide RHP if either the condition is true OR the reportID is marked as an expense. - */ - useEffect(() => { - // Check if we should show wide RHP based on condition OR if reportID is in optimistic set - if (condition || (reportID && isReportIDMarkedAsMultiTransactionExpense(reportID))) { - showSuperWideRHPVersion(route); - return; - } - showWideRHPVersion(route); - }, [condition, reportID, isReportIDMarkedAsExpense, route, showWideRHPVersion, showSuperWideRHPVersion, isReportIDMarkedAsMultiTransactionExpense]); -} - WideRHPContextProvider.displayName = 'WideRHPContextProvider'; export default WideRHPContextProvider; -export { - calculateReceiptPaneRHPWidth, - calculateSuperWideRHPWidth, - animatedSuperWideRHPWidth, - modalStackOverlaySuperWideRHPWidth, - modalStackOverlayWideRHPWidth, - animatedReceiptPaneRHPWidth, - secondOverlayProgress, - thirdOverlayProgress, - useShowSuperWideRHPVersion, - useShowWideRHPVersion, - WideRHPContext, - animatedWideRHPWidth, - innerRHPProgress, - expandedRHPProgress, - WIDE_RIGHT_MODALS, - SUPER_WIDE_RIGHT_MODALS, -}; +export {expandedRHPProgress, receiptPaneRHPWidth, secondOverlayProgress, useShowWideRHPVersion, WideRHPContext, extractNavigationKeys}; diff --git a/src/components/WideRHPContextProvider/types.ts b/src/components/WideRHPContextProvider/types.ts index d3e3154eb297b..630195bda6ad1 100644 --- a/src/components/WideRHPContextProvider/types.ts +++ b/src/components/WideRHPContextProvider/types.ts @@ -1,65 +1,34 @@ +// eslint-disable-next-line no-restricted-imports +import type {Animated} from 'react-native'; import type {NavigationRoute} from '@libs/Navigation/types'; type WideRHPContextType = { // Route keys of screens that should be displayed in wide format wideRHPRouteKeys: string[]; - // Route keys of screens that should be displayed in super wide format - superWideRHPRouteKeys: string[]; + // Progress of changing format: 0 - narrow, 1 - wide + expandedRHPProgress: Animated.Value; + + // Progress of the secondary overlay, the one covering wider RHP screen + secondOverlayProgress: Animated.Value; // If the secondary overlay should be rendered. This value takes into account the delay of closing transition. shouldRenderSecondaryOverlay: boolean; - // If the tertiary overlay should be rendered. This value takes into account the delay of closing transition. - shouldRenderTertiaryOverlay: boolean; - // Show given route as in wide format showWideRHPVersion: (route: NavigationRoute) => void; - // Show given route as in super wide format - showSuperWideRHPVersion: (route: NavigationRoute) => void; - // Remove given route from the array cleanWideRHPRouteKey: (route: NavigationRoute) => void; // Mark reportID as expense before condition check markReportIDAsExpense: (reportID: string) => void; - // Mark reportID as multi-transaction expense before condition check - markReportIDAsMultiTransactionExpense: (reportID: string) => void; - - // Unmark reportID as multi-transaction expense before condition check - unmarkReportIDAsMultiTransactionExpense: (reportID: string) => void; - // Check if reportID is marked as expense isReportIDMarkedAsExpense: (reportID: string) => boolean; - // Check if reportID is marked as multi-transaction expense - isReportIDMarkedAsMultiTransactionExpense: (reportID: string) => boolean; - - // Navigate to the first visible RHP - dismissToFirstRHP: () => void; - - // Navigate to the second visible RHP - dismissToSecondRHP: () => void; - - // Whether the currently focused route is inside the wide RHP set - isWideRHPFocused: boolean; - - // Whether the wide rhp modal is closing - isWideRHPClosing: boolean; - - // Mark that wide rhp is being closed - setIsWideRHPClosing: (isClosing: boolean) => void; - - // Sync wide RHP keys with the visible RHP screens - syncWideRHPKeys: () => void; - - // Sync super wide RHP keys with the visible RHP screens - syncSuperWideRHPKeys: () => void; - - // Clear the arrays of wide and super wide rhp keys - clearWideRHPKeys: () => void; + // Navigate to the last element in wideRHPRouteKeys array + dismissToWideReport: () => void; }; // eslint-disable-next-line import/prefer-default-export diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 407ca33a47cc6..a1b32d5f28d2b 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -154,7 +154,7 @@ function AuthScreens() { const {initialURL, isAuthenticatedAtStartup, setIsAuthenticatedAtStartup} = useContext(InitialURLContext); const modalCardStyleInterpolator = useModalCardStyleInterpolator(); const archivedReportsIdSet = useArchivedReportsIdSet(); - const {shouldRenderSecondaryOverlay, shouldRenderTertiaryOverlay, dismissToFirstRHP, dismissToSecondRHP} = useContext(WideRHPContext); + const {shouldRenderSecondaryOverlay, dismissToWideReport} = useContext(WideRHPContext); // State to track whether the delegator's authentication is completed before displaying data const [isDelegatorFromOldDotIsReady, setIsDelegatorFromOldDotIsReady] = useState(false); @@ -342,12 +342,7 @@ function AuthScreens() { } if (shouldRenderSecondaryOverlay) { - dismissToFirstRHP(); - return; - } - - if (shouldRenderTertiaryOverlay) { - dismissToSecondRHP(); + dismissToWideReport(); return; } @@ -359,7 +354,7 @@ function AuthScreens() { true, ); return () => unsubscribeEscapeKey(); - }, [dismissToFirstRHP, dismissToSecondRHP, modal?.disableDismissOnEscape, modal?.willAlertModalBecomeVisible, shouldRenderSecondaryOverlay, shouldRenderTertiaryOverlay]); + }, [dismissToWideReport, modal?.disableDismissOnEscape, modal?.willAlertModalBecomeVisible, shouldRenderSecondaryOverlay]); // Animation is disabled when navigating to the sidebar screen const getWorkspaceOrDomainSplitNavigatorOptions = ({route}: {route: RouteProp}) => { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index acd80faf5b4ca..2b8a6bd455293 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -1,15 +1,8 @@ -import {useIsFocused, useRoute} from '@react-navigation/native'; +import {useRoute} from '@react-navigation/native'; import type {ParamListBase} from '@react-navigation/routers'; -import React, {useCallback, useContext, useMemo} from 'react'; +import React, {useCallback, useContext} from 'react'; import {View} from 'react-native'; -import { - animatedReceiptPaneRHPWidth, - modalStackOverlaySuperWideRHPWidth, - modalStackOverlayWideRHPWidth, - secondOverlayProgress, - thirdOverlayProgress, - WideRHPContext, -} from '@components/WideRHPContextProvider'; +import {WideRHPContext} from '@components/WideRHPContextProvider'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; @@ -23,7 +16,6 @@ import type { DebugParamList, EditRequestNavigatorParamList, EnablePaymentsNavigatorParamList, - ExpenseReportNavigatorParamList, FlagCommentNavigatorParamList, MergeTransactionNavigatorParamList, MissingPersonalDetailsParamList, @@ -44,8 +36,6 @@ import type { RoomMembersNavigatorParamList, ScheduleCallParamList, SearchAdvancedFiltersParamList, - SearchMoneyRequestReportParamList, - SearchReportActionsParamList, SearchReportParamList, SearchSavedSearchParamList, SettingsNavigatorParamList, @@ -94,14 +84,6 @@ const OPTIONS_PER_SCREEN: Partial }, }; -function isSuperWideRHPRouteName(routeName: string) { - return routeName === SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT || routeName === SCREENS.RIGHT_MODAL.EXPENSE_REPORT; -} - -function isWideRHPRouteName(routeName: string) { - return routeName === SCREENS.RIGHT_MODAL.SEARCH_REPORT; -} - /** * Create a modal stack navigator with an array of sub-screens. * @@ -113,11 +95,9 @@ function createModalStackNavigator(screens: Scr function ModalStack() { const styles = useThemeStyles(); const screenOptions = useModalStackScreenOptions(); - const {shouldRenderSecondaryOverlay, shouldRenderTertiaryOverlay, isWideRHPFocused, superWideRHPRouteKeys, isWideRHPClosing} = useContext(WideRHPContext); + const {secondOverlayProgress, shouldRenderSecondaryOverlay} = useContext(WideRHPContext); const route = useRoute(); - const isFocused = useIsFocused(); - // We have to use the isSmallScreenWidth instead of shouldUseNarrow layout, because we want to have information about screen width without the context of side modal. // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); @@ -133,16 +113,6 @@ function createModalStackNavigator(screens: Scr [screenOptions], ); - const isRHPDisplayedOnWideRHP = useMemo( - () => !isSmallScreenWidth && !isWideRHPFocused && !isWideRHPClosing && shouldRenderSecondaryOverlay && (isSuperWideRHPRouteName(route.name) || isWideRHPRouteName(route.name)), - [isSmallScreenWidth, isWideRHPClosing, isWideRHPFocused, route.name, shouldRenderSecondaryOverlay], - ); - - const isWideRHPDisplayedOnSuperWideRHP = useMemo( - () => !isSmallScreenWidth && !isFocused && !!isWideRHPFocused && shouldRenderSecondaryOverlay && isSuperWideRHPRouteName(route.name), - [isFocused, isSmallScreenWidth, isWideRHPFocused, route.name, shouldRenderSecondaryOverlay], - ); - return ( // This container is necessary to hide card translation during transition. Without it the user would see un-clipped cards. @@ -157,32 +127,11 @@ function createModalStackNavigator(screens: Scr /> ))} - {/* These overlays are used to cover the space under the narrower RHP screen when more than one RHP width is displayed on the screen */} - {/* Their position is calculated as follows: */} - {/* The width of the window for which we calculate the overlay positions is the width of the RHP window, for example for Super Wide RHP it will be 1260 px on a wide layout. */} - {/* We need to move the overlay left from the left edge of the RHP below to the left edge of the RHP above. */} - {/* To calculate this, subtract the width of the widest RHP from the width of the RHP above. */} - {/* Two cases were described for the secondary overlay: */} - {/* 1. Single RHP is displayed on Wide RHP (Super Wide or Wide) - here we additionally check the length of superWideRHPRouteKeys because Super Wide RHP route can also be displayed in Wide RHP when the number of visible transactions is less than 2. */} - {/* 2. Wide RHP is displayed on Super Wide RHP route. */} - {/* Please note that in these cases, the overlay is rendered from the RHP screen displayed below. For example, if we display RHP on Wide RHP, the secondary overlay is rendered from Wide RHP, etc. */} - {/* There is also a special case where three different RHP widths are displayed at the same time. In this case, an overlay under RHP should be rendered from Wide RHP. */} - {isRHPDisplayedOnWideRHP ? ( - 0 ? modalStackOverlaySuperWideRHPWidth : animatedReceiptPaneRHPWidth} - /> - ) : null} - {isWideRHPDisplayedOnSuperWideRHP ? ( + {!isSmallScreenWidth && shouldRenderSecondaryOverlay && route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT ? ( + // This overlay is necessary to cover the gap under the narrow format RHP screen - ) : null} - {!isSmallScreenWidth && shouldRenderTertiaryOverlay && isWideRHPRouteName(route.name) ? ( - ) : null} @@ -892,7 +841,8 @@ const MergeTransactionStackNavigator = createModalStackNavigator require('../../../../pages/TransactionMerge/ConfirmationPage').default, }); -const SearchReportActionsModalStackNavigator = createModalStackNavigator({ +const SearchReportModalStackNavigator = createModalStackNavigator({ + [SCREENS.SEARCH.REPORT_RHP]: () => require('../../../../pages/home/ReportScreen').default, [SCREENS.SEARCH.ROOT_VERIFY_ACCOUNT]: () => require('../../../../pages/Search/SearchRootVerifyAccountPage').default, [SCREENS.SEARCH.MONEY_REQUEST_REPORT_VERIFY_ACCOUNT]: () => require('../../../../pages/Search/SearchMoneyRequestReportVerifyAccountPage').default, [SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: () => require('../../../../pages/Search/SearchHoldReasonPage').default, @@ -900,16 +850,6 @@ const SearchReportActionsModalStackNavigator = createModalStackNavigator require('../../../../pages/Search/SearchTransactionsChangeReport').default, }); -// This navigator is reserved for the screen that can be displayed as Wide RHP, other screens should not be added here. -const SearchReportModalStackNavigator = createModalStackNavigator({ - [SCREENS.SEARCH.REPORT_RHP]: () => require('../../../../pages/home/ReportScreen').default, -}); - -// // This navigator is reserved for the screen that can be displayed as Super Wide RHP, other screens should not be added here. -const SearchMoneyRequestReportModalStackNavigator = createModalStackNavigator({ - [SCREENS.SEARCH.MONEY_REQUEST_REPORT]: () => require('../../../../pages/Search/SearchMoneyRequestReportPage').default, -}); - const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator({ [SCREENS.SEARCH.ADVANCED_FILTERS_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersTypePage').default, @@ -994,10 +934,6 @@ const ScheduleCallModalStackNavigator = createModalStackNavigator require('../../../../pages/ScheduleCall/ScheduleCallConfirmationPage').default, }); -const ExpenseReportModalStackNavigator = createModalStackNavigator({ - [SCREENS.EXPENSE_REPORT_RHP]: () => require('../../../../pages/Search/SearchMoneyRequestReportPage').default, -}); - const WorkspacesDomainModalStackNavigator = createModalStackNavigator({ [SCREENS.WORKSPACES_VERIFY_DOMAIN]: () => require('../../../../pages/domain/WorkspacesVerifyDomainPage').default, [SCREENS.WORKSPACES_DOMAIN_VERIFIED]: () => require('../../../../pages/domain/WorkspacesDomainVerifiedPage').default, @@ -1050,7 +986,4 @@ export { ScheduleCallModalStackNavigator, MergeTransactionStackNavigator, WorkspacesDomainModalStackNavigator, - SearchMoneyRequestReportModalStackNavigator, - ExpenseReportModalStackNavigator, - SearchReportActionsModalStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions.ts b/src/libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions.ts index c87c8f79c2e5d..7703f6712eddf 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions.ts @@ -16,17 +16,13 @@ function useModalStackScreenOptions() { // https://github.com/Expensify/App/issues/63747 // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); - const {wideRHPRouteKeys, superWideRHPRouteKeys} = useContext(WideRHPContext); + const {wideRHPRouteKeys} = useContext(WideRHPContext); return useCallback<({route}: {route: PlatformStackRouteProp}) => PlatformStackNavigationOptions>( ({route}) => { let cardStyleInterpolator; - if (superWideRHPRouteKeys.includes(route.key) && !isSmallScreenWidth) { - cardStyleInterpolator = enhanceCardStyleInterpolator(CardStyleInterpolators.forHorizontalIOS, { - cardStyle: styles.superWideRHPExtendedCardInterpolatorStyles, - }); - } else if (wideRHPRouteKeys.includes(route.key) && !isSmallScreenWidth) { + if (wideRHPRouteKeys.includes(route.key) && !isSmallScreenWidth) { // We need to use interpolator styles instead of regular card styles so we can use animated value for width. // It is necessary to have responsive width of the wide RHP for range 800px to 840px. cardStyleInterpolator = enhanceCardStyleInterpolator(CardStyleInterpolators.forHorizontalIOS, { @@ -49,7 +45,7 @@ function useModalStackScreenOptions() { }, }; }, - [isSmallScreenWidth, styles, superWideRHPRouteKeys, wideRHPRouteKeys], + [isSmallScreenWidth, styles.navigationScreenCardStyle, styles.wideRHPExtendedCardInterpolatorStyles, wideRHPRouteKeys], ); } diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx b/src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx index 5356f59e620b6..eb760709ddbb3 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx @@ -4,35 +4,41 @@ import React from 'react'; import {Animated, View} from 'react-native'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import useLocalize from '@hooks/useLocalize'; +import useSidePanel from '@hooks/useSidePanel'; import useThemeStyles from '@hooks/useThemeStyles'; import type {OverlayStylesParams} from '@styles/index'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; type BaseOverlayProps = { /* Callback to close the modal */ onPress?: () => void; - /* Override the progress from useCardAnimation. Necessary for the secondary overlay */ - progress?: OverlayStylesParams; + /* Whether there should be a gap on the right side. Necessary for the overlay that covers wider part of RHP. */ + hasMarginRight?: boolean; - /* Overlay position from the left edge of the container */ - positionLeftValue?: number | Animated.Value; + /* Whether there should be a gap on the left. Necessary for the overlay in modal stack navigator. */ + hasMarginLeft?: boolean; - /* Overlay position from the right edge of the container */ - positionRightValue?: number | Animated.Value; + /* Override the progress from useCardAnimation. Necessary for the secondary overlay */ + progress?: OverlayStylesParams; }; -// The default value of positionLeftValue is equal to -2 * variables.sideBarWidth, because we need to stretch the overlay to cover the sidebar and the translate animation distance. -function BaseOverlay({onPress, progress, positionLeftValue = -2 * variables.sideBarWidth, positionRightValue = 0}: BaseOverlayProps) { +function BaseOverlay({onPress, hasMarginRight = false, progress, hasMarginLeft = false}: BaseOverlayProps) { const styles = useThemeStyles(); const {current} = useCardAnimation(); const {translate} = useLocalize(); + const {sidePanelTranslateX} = useSidePanel(); return ( {/* In the latest Electron version buttons can't be both clickable and draggable. diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index c2310c7ac6f02..1e2b5003d38a4 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -1,34 +1,21 @@ import type {NavigatorScreenParams} from '@react-navigation/native'; -import {useFocusEffect} from '@react-navigation/native'; -import type {StackCardInterpolationProps} from '@react-navigation/stack'; import React, {useCallback, useContext, useMemo, useRef} from 'react'; +// We use Animated for all functionality related to wide RHP to make it easier +// to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. // eslint-disable-next-line no-restricted-imports import {Animated, InteractionManager} from 'react-native'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; -import { - animatedWideRHPWidth, - calculateReceiptPaneRHPWidth, - calculateSuperWideRHPWidth, - expandedRHPProgress, - innerRHPProgress, - secondOverlayProgress, - thirdOverlayProgress, - WideRHPContext, -} from '@components/WideRHPContextProvider'; +import {expandedRHPProgress, WideRHPContext} from '@components/WideRHPContextProvider'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import {abandonReviewDuplicateTransactions} from '@libs/actions/Transaction'; import {clearTwoFactorAuthData} from '@libs/actions/TwoFactorAuthActions'; import hideKeyboardOnSwipe from '@libs/Navigation/AppNavigator/hideKeyboardOnSwipe'; import * as ModalStackNavigators from '@libs/Navigation/AppNavigator/ModalStackNavigators'; -import useModalCardStyleInterpolator from '@libs/Navigation/AppNavigator/useModalCardStyleInterpolator'; import useRHPScreenOptions from '@libs/Navigation/AppNavigator/useRHPScreenOptions'; -import {navigationRef} from '@libs/Navigation/Navigation'; import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {AuthScreensParamList, RightModalNavigatorParamList} from '@navigation/types'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; @@ -39,41 +26,12 @@ type RightModalNavigatorProps = PlatformStackScreenProps(); -const singleRHPWidth = variables.sideBarWidth; -const getWideRHPWidth = (windowWidth: number) => variables.sideBarWidth + calculateReceiptPaneRHPWidth(windowWidth); - function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const isExecutingRef = useRef(false); const screenOptions = useRHPScreenOptions(); - const { - shouldRenderSecondaryOverlay, - isWideRHPFocused, - shouldRenderTertiaryOverlay, - isWideRHPClosing, - clearWideRHPKeys, - dismissToFirstRHP, - dismissToSecondRHP, - syncWideRHPKeys, - syncSuperWideRHPKeys, - } = useContext(WideRHPContext); - const {windowWidth} = useWindowDimensions(); - const modalCardStyleInterpolator = useModalCardStyleInterpolator(); - const styles = useThemeStyles(); - - const animatedWidth = expandedRHPProgress.interpolate({ - inputRange: [0, 1, 2], - outputRange: [singleRHPWidth, getWideRHPWidth(windowWidth), calculateSuperWideRHPWidth(windowWidth)], - }); - - const animatedWidthStyle = useMemo(() => { - return { - width: shouldUseNarrowLayout ? '100%' : animatedWidth, - } as const; - }, [animatedWidth, shouldUseNarrowLayout]); - - const overlayPositionLeft = useMemo(() => -1 * calculateSuperWideRHPWidth(windowWidth), [windowWidth]); + const {shouldRenderSecondaryOverlay, secondOverlayProgress, dismissToWideReport} = useContext(WideRHPContext); const screenListeners = useMemo( () => ({ @@ -107,33 +65,13 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { }, CONST.ANIMATED_TRANSITION); }, [navigation]); - useFocusEffect( - useCallback(() => { - syncWideRHPKeys(); - syncSuperWideRHPKeys(); - - return () => { - const isRhpClosed = !navigationRef?.getRootState()?.routes?.some((rootStateRoute) => rootStateRoute.key === route.key); - if (isRhpClosed) { - return; - } - clearWideRHPKeys(); - }; - }, [syncWideRHPKeys, syncSuperWideRHPKeys, clearWideRHPKeys, route.key]), - ); - return ( - {!shouldUseNarrowLayout && ( - - )} + {!shouldUseNarrowLayout && } {/* This one is to limit the outer Animated.View and allow the background to be pressable */} {/* Without it, the transparent half of the narrow format RHP card would cover the pressable part of the overlay */} - + - // Add 1 to change range from [0, 1] to [1, 2] - // Don't use outputMultiplier for the narrow layout - modalCardStyleInterpolator({ - props, - shouldAnimateSidePanel: true, - - // Adjust output range to match the wide RHP size - outputRangeMultiplier: isSmallScreenWidth - ? undefined - : Animated.add(Animated.multiply(innerRHPProgress, variables.receiptPaneRHPMaxWidth / variables.sideBarWidth), 1), - }), - }, - }} - /> - - - - {/* The third and second overlays are displayed here to cover RHP screens wider than the currently focused screen. */} - {/* Clicking on these overlays redirects you to the RHP screen below them. */} - {/* The width of these overlays is equal to the width of the screen minus the width of the currently focused RHP screen (positionRightValue) */} - {shouldRenderSecondaryOverlay && !shouldUseNarrowLayout && !isWideRHPFocused && !isWideRHPClosing && ( + {/* The second overlay is here to cover the wide rhp screen underneath */} + {/* It has a gap on the right to make the last rhp route (narrow) visible and pressable */} + {shouldRenderSecondaryOverlay && !shouldUseNarrowLayout && ( - )} - {shouldRenderSecondaryOverlay && !shouldUseNarrowLayout && !!isWideRHPFocused && ( - - )} - {shouldRenderTertiaryOverlay && !shouldUseNarrowLayout && ( - )} diff --git a/src/libs/Navigation/AppNavigator/Navigators/SearchFullscreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/SearchFullscreenNavigator.tsx index f988fae0a49ef..437208bd36dbe 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/SearchFullscreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/SearchFullscreenNavigator.tsx @@ -12,6 +12,7 @@ import SCREENS from '@src/SCREENS'; import type ReactComponentModule from '@src/types/utils/ReactComponentModule'; const loadSearchPage = () => require('@pages/Search/SearchPage').default; +const loadSearchMoneyReportPage = () => require('@pages/Search/SearchMoneyRequestReportPage').default; const Stack = createSearchFullscreenNavigator(); @@ -35,6 +36,10 @@ function SearchFullscreenNavigator({route}: PlatformStackScreenProps + ); diff --git a/src/libs/Navigation/AppNavigator/useRootNavigatorScreenOptions.ts b/src/libs/Navigation/AppNavigator/useRootNavigatorScreenOptions.ts index 22c18b2fc6cd2..b5c8edca41000 100644 --- a/src/libs/Navigation/AppNavigator/useRootNavigatorScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/useRootNavigatorScreenOptions.ts @@ -3,11 +3,10 @@ import type {StackCardInterpolationProps} from '@react-navigation/stack'; // to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. // eslint-disable-next-line no-restricted-imports import {Animated} from 'react-native'; -import {calculateSuperWideRHPWidth, expandedRHPProgress} from '@components/WideRHPContextProvider'; +import {expandedRHPProgress} from '@components/WideRHPContextProvider'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation'; import Presentation from '@libs/Navigation/PlatformStackNavigation/navigationOptions/presentation'; import type {PlatformStackNavigationOptions} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -29,21 +28,12 @@ const commonScreenOptions: PlatformStackNavigationOptions = { }, }; -function abs(a: Animated.AnimatedSubtraction) { - const b = Animated.multiply(a, -1); - const clampedA = Animated.diffClamp(a, 0, Number.MAX_SAFE_INTEGER); - const clampedB = Animated.diffClamp(b, 0, Number.MAX_SAFE_INTEGER); - - return Animated.add(clampedA, clampedB); -} - const useRootNavigatorScreenOptions = () => { const StyleUtils = useStyleUtils(); const modalCardStyleInterpolator = useModalCardStyleInterpolator(); // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const themeStyles = useThemeStyles(); - const {windowWidth} = useWindowDimensions(); return { rightModalNavigator: { @@ -55,31 +45,16 @@ const useRootNavigatorScreenOptions = () => { web: { presentation: Presentation.TRANSPARENT_MODAL, cardStyleInterpolator: (props: StackCardInterpolationProps) => + // Add 1 to change range from [0, 1] to [1, 2] // Don't use outputMultiplier for the narrow layout modalCardStyleInterpolator({ props, shouldAnimateSidePanel: true, - // On a wide layout, the output range multiplier is multiplied inside useModalCardStyleInterpolator by the width of a single RHP. - // Depending on the value of expandedRHPProgress, after multiplication the appropriate RHP width should be obtained. - // To achieve this, the following function was used: - // y = (1 - |x - 1|) * receiptPaneWidth/sidebarWidth + Max((x - 1), 0)) * (superWideRHPWidth / sidebarWidth - 1) + 1 - // For expandedRHPProgress equal to: - // 0 - Single RHP, y = 1 - // 1 - Wide RHP, y = receiptPaneWidth / sidebarWidth + 1 - // 2 - Super Wide RHP, y = superWideRHPWidth / sidebarWidth - // For the given values, after multiplying by sidebarWidth inside useModalCardStyleInterpolator, the correct widths are obtained. + + // Adjust output range to match the wide RHP size outputRangeMultiplier: isSmallScreenWidth ? undefined - : Animated.add( - Animated.add( - Animated.multiply(Animated.subtract(1, abs(Animated.subtract(1, expandedRHPProgress))), variables.receiptPaneRHPMaxWidth / variables.sideBarWidth), - Animated.multiply( - expandedRHPProgress.interpolate({inputRange: [1, 2], outputRange: [0, 1], extrapolate: 'clamp'}), - calculateSuperWideRHPWidth(windowWidth) / variables.sideBarWidth - 1, - ), - ), - 1, - ), + : Animated.add(Animated.multiply(expandedRHPProgress, variables.receiptPaneRHPMaxWidth / variables.sideBarWidth), 1), }), }, }, diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 47bbfa90b4d83..bf40c00d2b17c 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -197,16 +197,11 @@ function navigate(route: Route, options?: LinkToOptions) { } // Start a Sentry span for report navigation - if (route.startsWith('r/') || route.startsWith('search/r/') || route.startsWith('e/')) { - const reportIDMatch = route.match(/^(?:search\/)?(?:r|e)\/(\w+)/); - const reportID = reportIDMatch?.at(1); - if (reportID) { - let spanName = '/r/*'; - if (route.startsWith('search/r/')) { - spanName = '/search/r/*'; - } else if (route.startsWith('e/')) { - spanName = '/e/*'; - } + if (route.startsWith('r/') || route.startsWith('search/r/')) { + const reportIDMatch = route.match(/^(?:search\/)?r\/(\w+)/); + if (reportIDMatch?.at(1)) { + const reportID = reportIDMatch.at(1); + const spanName = route.startsWith('r/') ? '/r/*' : '/search/r/*'; startSpan(`${CONST.TELEMETRY.SPAN_OPEN_REPORT}_${reportID}`, { name: spanName, op: CONST.TELEMETRY.SPAN_OPEN_REPORT, @@ -216,8 +211,10 @@ function navigate(route: Route, options?: LinkToOptions) { }); } } + linkTo(navigationRef.current, route, options); } + /** * When routes are compared to determine whether the fallback route passed to the goUp function is in the state, * these parameters shouldn't be included in the comparison. @@ -604,30 +601,6 @@ const dismissModalWithReport = ({reportID, reportActionID, referrer, backTo}: Re }); }; -/** - * Returns to the first screen in the Right Hand Modal stack, dismissing all the others. - */ -const dismissToFirstRHP = () => { - const rootState = navigationRef.getRootState(); - if (!rootState) { - return; - } - - const rhpState = rootState.routes.findLast((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR)?.state; - - if (!rhpState) { - return; - } - - const routesToPop = rhpState.routes.length - 1; - if (routesToPop <= 0) { - dismissModal(); - return; - } - - navigationRef.dispatch({...StackActions.pop(routesToPop), target: rhpState.key}); -}; - /** * Returns to the first screen in the stack, dismissing all the others, only if the global variable shouldPopToSidebar is set to true. */ @@ -767,7 +740,6 @@ export default { onModalDismissedOnce, fireModalDismissed, isValidateLoginFlow, - dismissToFirstRHP, }; export {navigationRef}; diff --git a/src/libs/Navigation/helpers/isReportOpenInRHP.ts b/src/libs/Navigation/helpers/isReportOpenInRHP.ts index 031cb4546c76a..51e8a95bb66bd 100644 --- a/src/libs/Navigation/helpers/isReportOpenInRHP.ts +++ b/src/libs/Navigation/helpers/isReportOpenInRHP.ts @@ -1,5 +1,4 @@ import type {NavigationState} from '@react-navigation/native'; -import {WIDE_RIGHT_MODALS} from '@components/WideRHPContextProvider'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; @@ -12,7 +11,7 @@ const isReportOpenInRHP = (state: NavigationState | undefined): boolean => { if (params && 'screen' in params && typeof params.screen === 'string' && params.screen === SCREENS.RIGHT_MODAL.SEARCH_REPORT) { return true; } - return !!(lastRoute.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && lastRoute.state?.routes?.some((route) => WIDE_RIGHT_MODALS.has(route.name))); + return !!(lastRoute.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && lastRoute.state?.routes?.some((route) => route?.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT)); }; export default isReportOpenInRHP; diff --git a/src/libs/Navigation/helpers/isReportOpenInSuperWideRHP.ts b/src/libs/Navigation/helpers/isReportOpenInSuperWideRHP.ts deleted file mode 100644 index 368d4cf508bb7..0000000000000 --- a/src/libs/Navigation/helpers/isReportOpenInSuperWideRHP.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type {NavigationState} from '@react-navigation/native'; -import {SUPER_WIDE_RIGHT_MODALS} from '@components/WideRHPContextProvider'; -import NAVIGATORS from '@src/NAVIGATORS'; -import SCREENS from '@src/SCREENS'; - -const isReportOpenInSuperWideRHP = (state: NavigationState | undefined): boolean => { - const lastRoute = state?.routes?.at(-1); - if (!lastRoute) { - return false; - } - const params = lastRoute.params; - if (params && 'screen' in params && typeof params.screen === 'string' && params.screen === SCREENS.RIGHT_MODAL.SEARCH_REPORT) { - return true; - } - return !!(lastRoute.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && lastRoute.state?.routes?.some((route) => SUPER_WIDE_RIGHT_MODALS.has(route.name))); -}; - -export default isReportOpenInSuperWideRHP; diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts index 5e56b25342590..a733606877875 100644 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts @@ -49,7 +49,6 @@ const SEARCH_TO_RHP: Partial['config'] = { }, }, [SCREENS.RIGHT_MODAL.SEARCH_REPORT]: { - screens: { - [SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT.route, - }, - }, - [SCREENS.RIGHT_MODAL.SEARCH_REPORT_ACTIONS]: { screens: { [SCREENS.SEARCH.ROOT_VERIFY_ACCOUNT]: ROUTES.SEARCH_ROOT_VERIFY_ACCOUNT, + [SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT.route, [SCREENS.SEARCH.MONEY_REQUEST_REPORT_VERIFY_ACCOUNT]: ROUTES.SEARCH_MONEY_REQUEST_REPORT_VERIFY_ACCOUNT.route, [SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.route, [SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: ROUTES.TRANSACTION_HOLD_REASON_RHP, [SCREENS.SEARCH.TRANSACTIONS_CHANGE_REPORT_SEARCH_RHP]: ROUTES.MOVE_TRANSACTIONS_SEARCH_RHP, }, }, - [SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT]: { - screens: { - [SCREENS.SEARCH.MONEY_REQUEST_REPORT]: ROUTES.SEARCH_MONEY_REQUEST_REPORT.route, - }, - }, [SCREENS.RIGHT_MODAL.SEARCH_ADVANCED_FILTERS]: { screens: { [SCREENS.SEARCH.ADVANCED_FILTERS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS.getRoute(), @@ -1752,11 +1743,6 @@ const config: LinkingOptions['config'] = { [SCREENS.REPORT_CHANGE_APPROVER.ADD_APPROVER]: ROUTES.REPORT_CHANGE_APPROVER_ADD_APPROVER.route, }, }, - [SCREENS.RIGHT_MODAL.EXPENSE_REPORT]: { - screens: { - [SCREENS.EXPENSE_REPORT_RHP]: ROUTES.EXPENSE_REPORT_RHP.route, - }, - }, [SCREENS.RIGHT_MODAL.DOMAIN]: { screens: { [SCREENS.WORKSPACES_VERIFY_DOMAIN]: { @@ -1899,6 +1885,9 @@ const config: LinkingOptions['config'] = { [SCREENS.SEARCH.ROOT]: { path: ROUTES.SEARCH_ROOT.route, }, + [SCREENS.SEARCH.MONEY_REQUEST_REPORT]: { + path: ROUTES.SEARCH_MONEY_REQUEST_REPORT.route, + }, }, }, [NAVIGATORS.SHARE_MODAL_NAVIGATOR]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 659a0376b7182..7e824f63d55dd 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -2106,8 +2106,6 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.TRANSACTION_DUPLICATE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TRAVEL]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_REPORT]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.SEARCH_REPORT_ACTIONS]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_ADVANCED_FILTERS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_SAVED_SEARCH]: NavigatorScreenParams; @@ -2119,7 +2117,6 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.SCHEDULE_CALL]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.REPORT_CHANGE_APPROVER]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MERGE_TRANSACTION]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.EXPENSE_REPORT]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.DOMAIN]: NavigatorScreenParams; }; @@ -2587,7 +2584,13 @@ type AuthScreensParamList = SharedScreensParamList & [NAVIGATORS.TEST_TOOLS_MODAL_NAVIGATOR]: NavigatorScreenParams; }; -type SearchReportActionsParamList = { +type SearchReportParamList = { + [SCREENS.SEARCH.REPORT_RHP]: { + reportID: string; + reportActionID?: string; + // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md + backTo?: Routes; + }; [SCREENS.SEARCH.REPORT_VERIFY_ACCOUNT]: { reportID: string; }; @@ -2617,29 +2620,17 @@ type SearchReportActionsParamList = { }; }; -type SearchReportParamList = { - [SCREENS.SEARCH.REPORT_RHP]: { - reportID: string; - reportActionID?: string; - // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md - backTo?: Routes; - }; -}; - -type SearchMoneyRequestReportParamList = { - [SCREENS.SEARCH.MONEY_REQUEST_REPORT]: { - reportID: string; - // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md - backTo?: Routes; - }; -}; - type SearchFullscreenNavigatorParamList = { [SCREENS.SEARCH.ROOT]: { q: SearchQueryString; name?: string; groupBy?: string; }; + [SCREENS.SEARCH.MONEY_REQUEST_REPORT]: { + reportID: string; + // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md + backTo?: Routes; + }; }; type SearchAdvancedFiltersParamList = { @@ -2747,14 +2738,6 @@ type TestToolsModalModalNavigatorParamList = { }; }; -type ExpenseReportNavigatorParamList = { - [SCREENS.EXPENSE_REPORT_RHP]: { - reportID: string; - // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md - backTo?: Routes; - }; -}; - type RootNavigatorParamList = PublicScreensParamList & AuthScreensParamList & SearchFullscreenNavigatorParamList; type OnboardingFlowName = keyof OnboardingModalNavigatorParamList; @@ -2861,10 +2844,7 @@ export type { TestToolsModalModalNavigatorParamList, MergeTransactionNavigatorParamList, AttachmentModalScreensParamList, - SearchMoneyRequestReportParamList, - ExpenseReportNavigatorParamList, WorkspacesDomainModalNavigatorParamList, DomainSplitNavigatorParamList, DomainScreenName, - SearchReportActionsParamList, }; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index e53d9691252a7..c8b0415eb8930 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -68,7 +68,6 @@ import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import * as Localize from '@libs/Localize'; import Log from '@libs/Log'; import isReportOpenInRHP from '@libs/Navigation/helpers/isReportOpenInRHP'; -import isReportOpenInSuperWideRHP from '@libs/Navigation/helpers/isReportOpenInSuperWideRHP'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; // TODO: Replace onyx.connect with useOnyx hook (https://github.com/Expensify/App/issues/66365) @@ -971,11 +970,6 @@ function dismissModalAndOpenReportInInboxTab(reportID?: string) { const rhpKey = rootState.routes.at(-1)?.state?.key; if (rhpKey) { const hasMultipleTransactions = Object.values(allTransactions).filter((transaction) => transaction?.reportID === reportID).length > 0; - // When a report is opened in the super wide RHP, we need to dismiss to the first RHP to show the same report with new expense. - if (isReportOpenInSuperWideRHP(rootState)) { - Navigation.dismissToFirstRHP(); - return; - } // When a report with one expense is opened in the wide RHP and the user adds another expense, RHP should be dismissed and ROUTES.SEARCH_MONEY_REQUEST_REPORT should be displayed. if (hasMultipleTransactions && reportID) { Navigation.dismissModal(); @@ -14380,7 +14374,7 @@ function updateSplitTransactionsFromSplitExpensesFlow(params: UpdateSplitTransac const transactionThreadReportScreen = Navigation.getReportRouteByID(transactionThreadReportID); if (isSearchPageTopmostFullScreenRoute || !transactionReport?.parentReportID) { - Navigation.dismissToFirstRHP(); + Navigation.dismissModal(); // After the modal is dismissed, remove the transaction thread report screen // to avoid navigating back to a report removed by the split transaction. diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index 2eed5ed6dbd75..5b1344bcd0f1a 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -9,7 +9,7 @@ import {holdMoneyRequestOnSearch} from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; -import type {SearchReportActionsParamList} from '@navigation/types'; +import type {SearchReportParamList} from '@navigation/types'; import HoldReasonFormView from '@pages/iou/HoldReasonFormView'; import {putTransactionsOnHold} from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -17,8 +17,8 @@ import SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm'; type SearchHoldReasonPageProps = - | PlatformStackScreenProps - | PlatformStackScreenProps; + | PlatformStackScreenProps + | PlatformStackScreenProps; function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/Search/SearchMoneyRequestReportPage.tsx b/src/pages/Search/SearchMoneyRequestReportPage.tsx index fd0f8226f4dce..c20ef96f56a25 100644 --- a/src/pages/Search/SearchMoneyRequestReportPage.tsx +++ b/src/pages/Search/SearchMoneyRequestReportPage.tsx @@ -1,5 +1,5 @@ import {PortalHost} from '@gorhom/portal'; -import React, {useContext, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useEffect, useMemo, useRef, useState} from 'react'; import type {FlatList} from 'react-native'; import {View} from 'react-native'; import type {OnyxCollection} from 'react-native-onyx'; @@ -7,7 +7,6 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView import DragAndDropProvider from '@components/DragAndDrop/Provider'; import MoneyRequestReportView from '@components/MoneyRequestReportView/MoneyRequestReportView'; import ScreenWrapper from '@components/ScreenWrapper'; -import {useShowSuperWideRHPVersion, WideRHPContext} from '@components/WideRHPContextProvider'; import useIsReportReadyToDisplay from '@hooks/useIsReportReadyToDisplay'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -19,7 +18,7 @@ import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViol import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {getAllNonDeletedTransactions} from '@libs/MoneyRequestReportUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {ExpenseReportNavigatorParamList, SearchMoneyRequestReportParamList} from '@libs/Navigation/types'; +import type {SearchFullscreenNavigatorParamList} from '@libs/Navigation/types'; import {getFilteredReportActionsForReportView, getIOUActionForTransactionID, getOneTransactionThreadReportID} from '@libs/ReportActionsUtils'; import {isValidReportIDFromPath} from '@libs/ReportUtils'; import Navigation from '@navigation/Navigation'; @@ -33,9 +32,7 @@ import type SCREENS from '@src/SCREENS'; import type {Policy} from '@src/types/onyx'; import {getEmptyObject} from '@src/types/utils/EmptyObject'; -type SearchMoneyRequestPageProps = - | PlatformStackScreenProps - | PlatformStackScreenProps; +type SearchMoneyRequestPageProps = PlatformStackScreenProps; const defaultReportMetadata = { isLoadingInitialReportActions: true, @@ -50,7 +47,6 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const {superWideRHPRouteKeys} = useContext(WideRHPContext); const reportIDFromRoute = getNonEmptyStringOnyxID(route.params?.reportID); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`, {allowStaleData: true, canBeMissing: true}); @@ -82,11 +78,6 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) { const reportID = report?.reportID; - // If there is more than one transaction, display the report in Super Wide RHP, otherwise it will be shown in Wide RHP - const shouldShowSuperWideRHP = visibleTransactions.length > 1; - - useShowSuperWideRHPVersion(shouldShowSuperWideRHP); - useEffect(() => { if (transactionThreadReportID === CONST.FAKE_REPORT_ID && oneTransactionID) { const iouAction = getIOUActionForTransactionID(reportActions, oneTransactionID); @@ -102,10 +93,6 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) { // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useMemo( (): boolean => { - if (superWideRHPRouteKeys.length > 0) { - return false; - } - if (isLoadingApp !== false) { return false; } diff --git a/src/pages/Search/SearchMoneyRequestReportVerifyAccountPage.tsx b/src/pages/Search/SearchMoneyRequestReportVerifyAccountPage.tsx index fd82f44685dad..241b04324f902 100644 --- a/src/pages/Search/SearchMoneyRequestReportVerifyAccountPage.tsx +++ b/src/pages/Search/SearchMoneyRequestReportVerifyAccountPage.tsx @@ -1,11 +1,11 @@ import React from 'react'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {SearchReportActionsParamList} from '@libs/Navigation/types'; +import type {SearchReportParamList} from '@libs/Navigation/types'; import VerifyAccountPageBase from '@pages/settings/VerifyAccountPageBase'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -type SearchMoneyRequestReportVerifyAccountPageProps = PlatformStackScreenProps; +type SearchMoneyRequestReportVerifyAccountPageProps = PlatformStackScreenProps; function SearchMoneyRequestReportVerifyAccountPage({route}: SearchMoneyRequestReportVerifyAccountPageProps) { return ; diff --git a/src/pages/Search/SearchReportVerifyAccountPage.tsx b/src/pages/Search/SearchReportVerifyAccountPage.tsx index 94069384cf3c5..820f674dc3b5b 100644 --- a/src/pages/Search/SearchReportVerifyAccountPage.tsx +++ b/src/pages/Search/SearchReportVerifyAccountPage.tsx @@ -1,11 +1,11 @@ import React from 'react'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {SearchReportActionsParamList} from '@libs/Navigation/types'; +import type {SearchReportParamList} from '@libs/Navigation/types'; import VerifyAccountPageBase from '@pages/settings/VerifyAccountPageBase'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -type SearchReportVerifyAccountPageProps = PlatformStackScreenProps; +type SearchReportVerifyAccountPageProps = PlatformStackScreenProps; function SearchReportVerifyAccountPage({route}: SearchReportVerifyAccountPageProps) { return ; diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx index 98f23c6ad5bed..0f6b2dee426d4 100644 --- a/src/pages/TransactionMerge/ConfirmationPage.tsx +++ b/src/pages/TransactionMerge/ConfirmationPage.tsx @@ -92,7 +92,7 @@ function ConfirmationPage({route}: ConfirmationPageProps) { if (reportID !== targetTransaction.reportID && reportIDToDismiss) { Navigation.dismissModalWithReport({reportID: reportIDToDismiss}); } else { - Navigation.dismissToFirstRHP(); + Navigation.dismissModal(); } }, [targetTransaction, mergeTransaction, sourceTransaction, transactionID, targetTransactionThreadReportID, policy, policyTags, policyCategories]); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 4c2d60d41ea30..fc7707e3a7555 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -2,7 +2,7 @@ import {PortalHost} from '@gorhom/portal'; import {useIsFocused} from '@react-navigation/native'; import {accountIDSelector} from '@selectors/Session'; import {deepEqual} from 'fast-equals'; -import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {FlatList, ViewStyle} from 'react-native'; // We use Animated for all functionality related to wide RHP to make it easier // to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. @@ -20,7 +20,7 @@ import MoneyRequestReceiptView from '@components/ReportActionItem/MoneyRequestRe import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; -import {useShowWideRHPVersion, WideRHPContext} from '@components/WideRHPContextProvider'; +import {useShowWideRHPVersion} from '@components/WideRHPContextProvider'; import useAppFocusEvent from '@hooks/useAppFocusEvent'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useDeepCompareRef from '@hooks/useDeepCompareRef'; @@ -165,9 +165,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const {isBetaEnabled} = usePermissions(); const {isOffline} = useNetwork(); const {shouldUseNarrowLayout, isInNarrowPaneModal} = useResponsiveLayout(); - - const {wideRHPRouteKeys, superWideRHPRouteKeys} = useContext(WideRHPContext); - const isDisplayedInWidePaneModal = wideRHPRouteKeys.includes(route.key) || superWideRHPRouteKeys.includes(route.key); const currentReportIDValue = useCurrentReportID(); const [isComposerFullSize = false] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${reportIDFromRoute}`, {canBeMissing: true}); @@ -377,7 +374,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { Navigation.goBack(); return; } - if ((prioritizeBackTo && backTo) || isDisplayedInWidePaneModal) { + if (prioritizeBackTo && backTo) { Navigation.goBack(backTo as Route); return; } @@ -395,7 +392,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } Navigation.goBack(); }, - [isInNarrowPaneModal, isDisplayedInWidePaneModal, backTo], + [isInNarrowPaneModal, backTo], ); let headerView = ( @@ -414,9 +411,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { report={report} policy={policy} parentReportAction={parentReportAction} - onBackButtonPress={() => { - onBackButtonPress(true); - }} + onBackButtonPress={onBackButtonPress} /> ); } diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx index e28e86e923e23..e5682aa734591 100644 --- a/src/pages/iou/HoldReasonPage.tsx +++ b/src/pages/iou/HoldReasonPage.tsx @@ -7,7 +7,7 @@ import useOnyx from '@hooks/useOnyx'; import {addErrorMessage} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {MoneyRequestNavigatorParamList, SearchReportActionsParamList} from '@libs/Navigation/types'; +import type {MoneyRequestNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; import {getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {canEditMoneyRequest, isReportInGroupPolicy} from '@libs/ReportUtils'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; @@ -20,7 +20,7 @@ import HoldReasonFormView from './HoldReasonFormView'; type HoldReasonPageProps = | PlatformStackScreenProps - | PlatformStackScreenProps; + | PlatformStackScreenProps; function HoldReasonPage({route}: HoldReasonPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/iou/RejectReasonPage.tsx b/src/pages/iou/RejectReasonPage.tsx index 86ee773637243..62edf7b5ca964 100644 --- a/src/pages/iou/RejectReasonPage.tsx +++ b/src/pages/iou/RejectReasonPage.tsx @@ -4,7 +4,7 @@ import {useSearchContext} from '@components/Search/SearchContext'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {MoneyRequestNavigatorParamList, SearchReportActionsParamList} from '@libs/Navigation/types'; +import type {MoneyRequestNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import {clearErrorFields, clearErrors} from '@userActions/FormActions'; import {rejectMoneyRequest} from '@userActions/IOU'; @@ -15,7 +15,7 @@ import RejectReasonFormView from './RejectReasonFormView'; type RejectReasonPageProps = | PlatformStackScreenProps - | PlatformStackScreenProps; + | PlatformStackScreenProps; function RejectReasonPage({route}: RejectReasonPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index 6902cd5266955..cc54b30e27a09 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -134,12 +134,12 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const splitExpenseWithoutID = {...splitExpenses.at(0), transactionID: ''}; // When we try to save one split during splits creation and if the data is identical to the original transaction we should close the split flow if (!childTransactions.length && deepEqual(splitFieldDataFromOriginalTransactionWithoutID, splitExpenseWithoutID)) { - Navigation.dismissToFirstRHP(); + Navigation.dismissModal(); return; } // When we try to save splits during editing splits and if the data is identical to the already created transactions we should close the split flow if (childTransactions.length && deepEqual(splitFieldDataFromChildTransactions, splitExpenses)) { - Navigation.dismissToFirstRHP(); + Navigation.dismissModal(); return; } // When we try to save one split during splits creation and if the data is not identical to the original transaction we should show the error @@ -167,7 +167,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { // When we try to save splits during editing splits and if the data is identical to the already created transactions we should close the split flow if (deepEqual(splitFieldDataFromChildTransactions, splitExpenses)) { - Navigation.dismissToFirstRHP(); + Navigation.dismissModal(); return; } diff --git a/src/styles/index.ts b/src/styles/index.ts index 5ea4c100cebb2..9fa1dbf32e5da 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3,18 +3,19 @@ /* eslint-disable @typescript-eslint/naming-convention */ import type {LineLayerStyleProps} from '@rnmapbox/maps/src/utils/MapboxStyles'; import lodashClamp from 'lodash/clamp'; +import type {RefObject} from 'react'; import type {LineLayer} from 'react-map-gl'; import type {ImageStyle, TextStyle, ViewStyle} from 'react-native'; // eslint-disable-next-line no-restricted-imports import {Animated, Platform, StyleSheet} from 'react-native'; import type {PickerStyle} from 'react-native-picker-select'; -import type {SharedValue} from 'react-native-reanimated'; import {interpolate} from 'react-native-reanimated'; +import type {SharedValue} from 'react-native-reanimated'; import type {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; import type {ValueOf} from 'type-fest'; import type DotLottieAnimation from '@components/LottieAnimations/types'; import {ACTIVE_LABEL_SCALE} from '@components/TextInput/styleConst'; -import {animatedReceiptPaneRHPWidth, animatedSuperWideRHPWidth, animatedWideRHPWidth} from '@components/WideRHPContextProvider'; +import {receiptPaneRHPWidth} from '@components/WideRHPContextProvider'; import {getBrowser, isMobile, isMobileSafari, isSafari} from '@libs/Browser'; import getPlatform from '@libs/getPlatform'; import CONST from '@src/CONST'; @@ -5354,14 +5355,7 @@ const staticStyles = (theme: ThemeColors) => position: Platform.OS === 'web' ? 'fixed' : 'absolute', height: '100%', right: 0, - width: animatedWideRHPWidth, - }, - - superWideRHPExtendedCardInterpolatorStyles: { - position: Platform.OS === 'web' ? 'fixed' : 'absolute', - height: '100%', - right: 0, - width: animatedSuperWideRHPWidth, + width: Animated.add(variables.sideBarWidth, receiptPaneRHPWidth), }, flexibleHeight: { @@ -5375,7 +5369,7 @@ const staticStyles = (theme: ThemeColors) => wideRHPMoneyRequestReceiptViewContainer: { backgroundColor: theme.appBG, - width: animatedReceiptPaneRHPWidth, + width: receiptPaneRHPWidth, height: '100%', borderRightWidth: 1, borderColor: theme.border, @@ -5582,7 +5576,7 @@ const dynamicStyles = (theme: ThemeColors) => animatedRHPNavigatorContainerWidth: (shouldUseNarrowLayout: boolean, expandedRHPProgress: Animated.Value) => ({ - width: shouldUseNarrowLayout ? '100%' : Animated.add(variables.sideBarWidth, Animated.multiply(expandedRHPProgress, animatedReceiptPaneRHPWidth)), + width: shouldUseNarrowLayout ? '100%' : Animated.add(variables.sideBarWidth, Animated.multiply(expandedRHPProgress, receiptPaneRHPWidth)), }) satisfies ViewStyle, OnboardingNavigatorInnerView: (shouldUseNarrowLayout: boolean) => @@ -5623,17 +5617,19 @@ const dynamicStyles = (theme: ThemeColors) => overlayStyles: ({ progress, - positionLeftValue, - positionRightValue, + hasMarginRight = false, + hasMarginLeft = false, + sidePanelTranslateX, }: { progress: OverlayStylesParams; - positionLeftValue: number | Animated.Value; - positionRightValue: number | Animated.Value; + hasMarginRight?: boolean; + hasMarginLeft?: boolean; + sidePanelTranslateX?: RefObject; }) => ({ // We need to stretch the overlay to cover the sidebar and the translate animation distance. - left: positionLeftValue, - right: positionRightValue, + left: hasMarginLeft ? receiptPaneRHPWidth : -2 * variables.sideBarWidth, + right: hasMarginRight ? Animated.add(variables.sideBarWidth, sidePanelTranslateX ? Animated.subtract(variables.sideBarWidth, sidePanelTranslateX.current) : 0) : 0, opacity: progress.interpolate({ inputRange: [0, 1], outputRange: [0, variables.overlayOpacity], diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 9c7e46a30805a..e83efc8333758 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -103,7 +103,6 @@ export default { androidSafeAreaInsetsPercentage: 1, sideBarWidth: 375, receiptPaneRHPMaxWidth: 465, - superWideRHPMaxWidth: 1260, minScanTooltipWidth: 320, uploadViewMargin: 20, chooseFilesViewMargin: 8, diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 380f6fcfc9eb4..11c7285667433 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -120,7 +120,6 @@ const topMostReportID = '23423423'; jest.mock('@src/libs/Navigation/Navigation', () => ({ navigate: jest.fn(), dismissModal: jest.fn(), - dismissToFirstRHP: jest.fn(), dismissModalWithReport: jest.fn(), goBack: jest.fn(), getTopmostReportId: jest.fn(() => topMostReportID), diff --git a/tests/utils/TestNavigationContainer.tsx b/tests/utils/TestNavigationContainer.tsx index 37476a42bd199..0c3370f12de0c 100644 --- a/tests/utils/TestNavigationContainer.tsx +++ b/tests/utils/TestNavigationContainer.tsx @@ -114,6 +114,10 @@ function TestSearchFullscreenNavigator() { name={SCREENS.SEARCH.ROOT} getComponent={getEmptyComponent()} /> + ); }