From 55f95607860d01e5dcf4e21f8fdeb3ecccfe9406 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 16 Dec 2025 20:57:16 +0100 Subject: [PATCH 1/5] Flatten SCREENS.RIGHT_MODAL.SEARCH_REPORT --- contributingGuides/NAVIGATION.md | 2 +- src/components/MoneyReportHeader.tsx | 6 +- src/components/MoneyRequestHeader.tsx | 8 +- ...neyRequestReportTransactionsNavigation.tsx | 8 +- src/components/ParentNavigationSubtitle.tsx | 2 +- src/components/ScreenWrapper/index.tsx | 7 +- .../ScrollOffsetContextProvider.tsx | 2 +- .../playbackContextReportIDUtils.ts | 2 +- .../WIDE_RIGHT_MODALS.ts | 12 + .../WideRHPContextProvider/default.ts | 9 +- .../getIsRHPDisplayedBelow.ts | 18 ++ .../getIsWideRHPOpenedBelow.ts | 14 -- .../getVisibleRHPRouteKeys.ts | 38 ++- .../WideRHPContextProvider/index.native.tsx | 19 +- .../WideRHPContextProvider/index.tsx | 137 +++++----- .../WideRHPContextProvider/types.ts | 23 +- .../useShowSuperWideRHPVersion/index.ts | 31 ++- .../useShowWideRHPVersion/index.ts | 12 +- .../SecondaryOverlay.tsx | 68 +++++ .../WideRHPOverlayWrapper/TertiaryOverlay.tsx | 24 ++ .../WideRHPOverlayWrapper/index.native.ts | 8 + .../WideRHPOverlayWrapper/index.tsx | 50 ++++ .../Navigation/AppNavigator/AuthScreens.tsx | 28 ++- .../ModalStackNavigators/index.tsx | 118 ++------- .../useModalStackScreenOptions.ts | 34 +-- .../Navigators/RightModalNavigator.tsx | 105 ++++---- src/libs/Navigation/Navigation.ts | 24 +- .../helpers/extractNavigationKeys.ts | 14 +- .../linkingConfig/RELATIONS/SEARCH_TO_RHP.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 6 +- src/libs/Navigation/types.ts | 17 +- src/libs/ReportUtils.ts | 2 +- src/pages/home/HeaderView.tsx | 2 +- src/pages/home/ReportScreen.tsx | 236 ++++++++++-------- src/styles/index.ts | 11 +- 35 files changed, 649 insertions(+), 450 deletions(-) create mode 100644 src/components/WideRHPContextProvider/WIDE_RIGHT_MODALS.ts create mode 100644 src/components/WideRHPContextProvider/getIsRHPDisplayedBelow.ts delete mode 100644 src/components/WideRHPContextProvider/getIsWideRHPOpenedBelow.ts create mode 100644 src/components/WideRHPOverlayWrapper/SecondaryOverlay.tsx create mode 100644 src/components/WideRHPOverlayWrapper/TertiaryOverlay.tsx create mode 100644 src/components/WideRHPOverlayWrapper/index.native.ts create mode 100644 src/components/WideRHPOverlayWrapper/index.tsx diff --git a/contributingGuides/NAVIGATION.md b/contributingGuides/NAVIGATION.md index 6926c3da2420b..2fd461eb2060c 100644 --- a/contributingGuides/NAVIGATION.md +++ b/contributingGuides/NAVIGATION.md @@ -514,7 +514,7 @@ Considerations when removing `backTo` from a URL: ```ts type ReportScreenNavigationProps = | PlatformStackScreenProps - | PlatformStackScreenProps; + | PlatformStackScreenProps; ``` An example of a screen that is reused in several flows is `VerifyAccountPage`. diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index bcfbf3e51c240..ae7c23f11d996 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -44,7 +44,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, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; +import type {ReportsSplitNavigatorParamList, RightModalNavigatorParamList, SearchFullscreenNavigatorParamList} from '@libs/Navigation/types'; import {buildOptimisticNextStepForPreventSelfApprovalsEnabled, buildOptimisticNextStepForStrictPolicyRuleViolations} from '@libs/NextStepUtils'; import type {KYCFlowEvent, TriggerKYCFlow} from '@libs/PaymentUtils'; import {selectPaymentType} from '@libs/PaymentUtils'; @@ -193,7 +193,7 @@ function MoneyReportHeader({ const route = useRoute< | PlatformStackRouteProp | PlatformStackRouteProp - | PlatformStackRouteProp + | PlatformStackRouteProp >(); const {login: currentUserLogin, accountID, email} = useCurrentUserPersonalDetails(); const defaultExpensePolicy = useDefaultExpensePolicy(); @@ -476,7 +476,7 @@ function MoneyReportHeader({ const shouldShowLoadingBar = useLoadingBarVisibility(); const kycWallRef = useContext(KYCWallContext); - const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; + const isReportInRHP = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; const isReportInSearch = route.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT; const isReportSubmitter = isCurrentUserSubmitter(chatIOUReport); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 3949e18c98bfb..98fc6ea636bb9 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -26,7 +26,7 @@ import {setNameValuePair} from '@libs/actions/User'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {ReportsSplitNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; +import type {ReportsSplitNavigatorParamList, RightModalNavigatorParamList} from '@libs/Navigation/types'; import {getOriginalMessage, isMoneyRequestAction, isTrackExpenseAction} from '@libs/ReportActionsUtils'; import {getTransactionThreadPrimaryAction, isMarkAsResolvedAction} from '@libs/ReportPrimaryActionUtils'; import {getSecondaryTransactionThreadActions} from '@libs/ReportSecondaryActionUtils'; @@ -98,7 +98,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use a correct layout for the hold expense modal https://github.com/Expensify/App/pull/47990#issuecomment-2362382026 // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); - const route = useRoute | PlatformStackRouteProp>(); + const route = useRoute< + PlatformStackRouteProp | PlatformStackRouteProp + >(); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, { canBeMissing: false, }); @@ -143,7 +145,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); - const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; + const isReportInRHP = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT; const isFromReviewDuplicates = !!route.params.backTo?.replaceAll(/\?.*/g, '').endsWith('/duplicates/review'); const shouldDisplayTransactionNavigation = !!(reportID && isReportInRHP); const isParentReportArchived = useReportIsArchived(report?.parentReportID); diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx index 99ee999f2e0b8..f447710206c00 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx @@ -7,7 +7,7 @@ import {WideRHPContext} from '@components/WideRHPContextProvider'; import useOnyx from '@hooks/useOnyx'; import {createTransactionThreadReport, setOptimisticTransactionThread} from '@libs/actions/Report'; import {clearActiveTransactionIDs} from '@libs/actions/TransactionThreadNavigation'; -import type {SearchReportParamList} from '@libs/Navigation/types'; +import type {RightModalNavigatorParamList} from '@libs/Navigation/types'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import Navigation from '@navigation/Navigation'; import navigationRef from '@navigation/navigationRef'; @@ -106,7 +106,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR useEffect(() => { return () => { const focusedRoute = findFocusedRoute(navigationRef.getRootState()); - if (focusedRoute?.name === SCREENS.SEARCH.REPORT_RHP || focusedRoute?.name === SCREENS.TRANSACTION_DUPLICATE.REVIEW) { + if (focusedRoute?.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT || focusedRoute?.name === SCREENS.TRANSACTION_DUPLICATE.REVIEW) { return; } clearActiveTransactionIDs(); @@ -123,7 +123,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR let backTo = Navigation.getActiveRoute(); if (isFromReviewDuplicates) { const currentRoute = navigationRef.getCurrentRoute(); - const params = currentRoute?.params as SearchReportParamList[typeof SCREENS.SEARCH.REPORT_RHP] | undefined; + const params = currentRoute?.params as RightModalNavigatorParamList[typeof SCREENS.RIGHT_MODAL.SEARCH_REPORT] | undefined; backTo = params?.backTo ?? backTo; } const nextThreadReportID = nextParentReportAction?.childReportID; @@ -151,7 +151,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR let backTo = Navigation.getActiveRoute(); if (isFromReviewDuplicates) { const currentRoute = navigationRef.getCurrentRoute(); - const params = currentRoute?.params as SearchReportParamList[typeof SCREENS.SEARCH.REPORT_RHP] | undefined; + const params = currentRoute?.params as RightModalNavigatorParamList[typeof SCREENS.RIGHT_MODAL.SEARCH_REPORT] | undefined; backTo = params?.backTo ?? backTo; } const prevThreadReportID = prevParentReportAction?.childReportID; diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index c37e09bddb49e..1595047038379 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -90,7 +90,7 @@ function ParentNavigationSubtitle({ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`, {canBeMissing: false}); const isReportArchived = useReportIsArchived(report?.reportID); const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report, isReportArchived); - const isReportInRHP = currentRoute.name === SCREENS.SEARCH.REPORT_RHP; + const isReportInRHP = currentRoute.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT; const currentFullScreenRoute = useRootNavigationState((state) => state?.routes?.findLast((route) => isFullScreenName(route.name))); const hasAccessToParentReport = currentReport?.hasParentAccess !== false; diff --git a/src/components/ScreenWrapper/index.tsx b/src/components/ScreenWrapper/index.tsx index 32b2c67a949ab..eab2ac4a9cec9 100644 --- a/src/components/ScreenWrapper/index.tsx +++ b/src/components/ScreenWrapper/index.tsx @@ -21,7 +21,7 @@ import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; import NarrowPaneContext from '@libs/Navigation/AppNavigator/Navigators/NarrowPaneContext'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {ReportsSplitNavigatorParamList, RootNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; +import type {ReportsSplitNavigatorParamList, RightModalNavigatorParamList, RootNavigatorParamList} from '@libs/Navigation/types'; import {closeReactNativeApp} from '@userActions/HybridApp'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; @@ -50,7 +50,10 @@ type ScreenWrapperProps = Omit & * * This is required because transitionEnd event doesn't trigger in the testing environment. */ - navigation?: PlatformStackNavigationProp | PlatformStackNavigationProp | PlatformStackNavigationProp; + navigation?: + | PlatformStackNavigationProp + | PlatformStackNavigationProp + | PlatformStackNavigationProp; /** A unique ID to find the screen wrapper in tests */ testID: string; diff --git a/src/components/ScrollOffsetContextProvider.tsx b/src/components/ScrollOffsetContextProvider.tsx index 4530f8a762a51..0b1007bde2da1 100644 --- a/src/components/ScrollOffsetContextProvider.tsx +++ b/src/components/ScrollOffsetContextProvider.tsx @@ -107,7 +107,7 @@ function ScrollOffsetContextProvider({children}: ScrollOffsetContextProviderProp const routeName = focusedRoute?.name; const isSearchScreen = routeName === SCREENS.SEARCH.ROOT; - const isSearchMoneyRequestReport = routeName === SCREENS.SEARCH.MONEY_REQUEST_REPORT || routeName === SCREENS.SEARCH.REPORT_RHP; + const isSearchMoneyRequestReport = routeName === SCREENS.SEARCH.MONEY_REQUEST_REPORT || routeName === SCREENS.RIGHT_MODAL.SEARCH_REPORT; const scrollOffsetKeys = Object.keys(scrollOffsetsRef.current); diff --git a/src/components/VideoPlayerContexts/PlaybackContext/playbackContextReportIDUtils.ts b/src/components/VideoPlayerContexts/PlaybackContext/playbackContextReportIDUtils.ts index b8fce5c618ab5..aec9590bd6408 100644 --- a/src/components/VideoPlayerContexts/PlaybackContext/playbackContextReportIDUtils.ts +++ b/src/components/VideoPlayerContexts/PlaybackContext/playbackContextReportIDUtils.ts @@ -47,7 +47,7 @@ const getCurrentRouteReportID: (url: string) => string | ProtectedCurrentRouteRe return isFocusedRouteAChatThread ? firstReportThatHasURLInAttachments : focusedRouteReportID; }; -const screensWithReportID = [SCREENS.SEARCH.REPORT_RHP, SCREENS.REPORT, SCREENS.SEARCH.MONEY_REQUEST_REPORT, SCREENS.REPORT_ATTACHMENTS]; +const screensWithReportID = [SCREENS.RIGHT_MODAL.SEARCH_REPORT, SCREENS.REPORT, SCREENS.SEARCH.MONEY_REQUEST_REPORT, SCREENS.REPORT_ATTACHMENTS]; function hasReportIdInRouteParams(route: SearchRoute): route is RouteWithReportIDInParams { return !!route && !!route.params && !!screensWithReportID.find((screen) => screen === route.name) && 'reportID' in route.params; diff --git a/src/components/WideRHPContextProvider/WIDE_RIGHT_MODALS.ts b/src/components/WideRHPContextProvider/WIDE_RIGHT_MODALS.ts new file mode 100644 index 0000000000000..504226d362653 --- /dev/null +++ b/src/components/WideRHPContextProvider/WIDE_RIGHT_MODALS.ts @@ -0,0 +1,12 @@ +// These sets contain the names of wide and super wide right modals. +import SCREENS from '@src/SCREENS'; + +// Wide right modals: modals that can be either wide or regular RHP size +// Super wide right modals: modals that can be super wide size +// All wide right modals: all modals that can be wide size (combination of wide and super wide) + +const WIDE_RIGHT_MODALS = new Set([SCREENS.RIGHT_MODAL.SEARCH_REPORT]); +const SUPER_WIDE_RIGHT_MODALS = new Set([SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT, SCREENS.RIGHT_MODAL.EXPENSE_REPORT]); +const ALL_WIDE_RIGHT_MODALS = new Set([...WIDE_RIGHT_MODALS, ...SUPER_WIDE_RIGHT_MODALS]); + +export {WIDE_RIGHT_MODALS, SUPER_WIDE_RIGHT_MODALS, ALL_WIDE_RIGHT_MODALS}; diff --git a/src/components/WideRHPContextProvider/default.ts b/src/components/WideRHPContextProvider/default.ts index add24e5f1c903..08b70c3a08f12 100644 --- a/src/components/WideRHPContextProvider/default.ts +++ b/src/components/WideRHPContextProvider/default.ts @@ -2,7 +2,9 @@ import type {WideRHPContextType} from './types'; const defaultWideRHPContextValue: WideRHPContextType = { wideRHPRouteKeys: [], - shouldRenderSecondaryOverlay: false, + shouldRenderSecondaryOverlayForWideRHP: false, + shouldRenderSecondaryOverlayForRHPOnWideRHP: false, + shouldRenderSecondaryOverlayForRHPOnSuperWideRHP: false, showWideRHPVersion: () => {}, removeWideRHPRouteKey: () => {}, markReportIDAsExpense: () => {}, @@ -10,15 +12,12 @@ const defaultWideRHPContextValue: WideRHPContextType = { unmarkReportIDAsMultiTransactionExpense: () => {}, isReportIDMarkedAsExpense: () => false, isReportIDMarkedAsMultiTransactionExpense: () => false, - isWideRHPClosing: false, - setIsWideRHPClosing: () => {}, isWideRHPFocused: false, shouldRenderTertiaryOverlay: false, superWideRHPRouteKeys: [], showSuperWideRHPVersion: () => {}, removeSuperWideRHPRouteKey: () => {}, - syncWideRHPKeys: () => {}, - syncSuperWideRHPKeys: () => {}, + syncRHPKeys: () => {}, clearWideRHPKeys: () => {}, }; diff --git a/src/components/WideRHPContextProvider/getIsRHPDisplayedBelow.ts b/src/components/WideRHPContextProvider/getIsRHPDisplayedBelow.ts new file mode 100644 index 0000000000000..a8ea22c43b40e --- /dev/null +++ b/src/components/WideRHPContextProvider/getIsRHPDisplayedBelow.ts @@ -0,0 +1,18 @@ +import getVisibleRHPKeys from './getVisibleRHPRouteKeys'; + +// Helper function to determine if wide or super wide RHP is displayed below the currently focused route +export default function getIsRHPDisplayedBelow(focusedRouteKey: string | undefined, allSuperWideRHPRouteKeys: string[], allWideRHPRouteKeys: string[]) { + const {visibleSuperWideRHPRouteKeys, visibleWideRHPRouteKeys} = getVisibleRHPKeys(allSuperWideRHPRouteKeys, allWideRHPRouteKeys); + + if (!focusedRouteKey) { + return { + isWideRHPBelow: false, + isSuperWideRHPBelow: false, + }; + } + + return { + isWideRHPBelow: visibleWideRHPRouteKeys.length > 0 && !visibleWideRHPRouteKeys.includes(focusedRouteKey), + isSuperWideRHPBelow: visibleSuperWideRHPRouteKeys.length > 0 && !visibleSuperWideRHPRouteKeys.includes(focusedRouteKey), + }; +} diff --git a/src/components/WideRHPContextProvider/getIsWideRHPOpenedBelow.ts b/src/components/WideRHPContextProvider/getIsWideRHPOpenedBelow.ts deleted file mode 100644 index 0b69c05e27515..0000000000000 --- a/src/components/WideRHPContextProvider/getIsWideRHPOpenedBelow.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type {NavigationRoute} from '@libs/Navigation/types'; -import getVisibleWideRHPKeys from './getVisibleRHPRouteKeys'; - -function getIsWideRHPOpenedBelow(focusedRoute: NavigationRoute | undefined, allWideRHPKeys: string[]) { - // Shouldn't ever happen but for type safety - if (!focusedRoute?.key) { - return false; - } - - const visibleWideRHPRouteKeys = getVisibleWideRHPKeys(allWideRHPKeys); - return visibleWideRHPRouteKeys.length > 0 && !visibleWideRHPRouteKeys.includes(focusedRoute.key); -} - -export default getIsWideRHPOpenedBelow; diff --git a/src/components/WideRHPContextProvider/getVisibleRHPRouteKeys.ts b/src/components/WideRHPContextProvider/getVisibleRHPRouteKeys.ts index 7fa7c15a8052a..6c945a7fa5a35 100644 --- a/src/components/WideRHPContextProvider/getVisibleRHPRouteKeys.ts +++ b/src/components/WideRHPContextProvider/getVisibleRHPRouteKeys.ts @@ -2,33 +2,55 @@ import extractNavigationKeys from '@libs/Navigation/helpers/extractNavigationKey import getLastVisibleRHPRouteKey from '@libs/Navigation/helpers/getLastVisibleRHPRouteKey'; import {navigationRef} from '@libs/Navigation/Navigation'; +type VisibleRHPKeys = { + visibleWideRHPRouteKeys: string[]; + visibleSuperWideRHPRouteKeys: string[]; +}; + +const emptyRHPKeysState: VisibleRHPKeys = { + visibleWideRHPRouteKeys: [], + visibleSuperWideRHPRouteKeys: [], +}; + /** * Extracts the keys of the screens that are currently displayed from the array of all Wide/Super Wide RHP keys * * @param allWideRHPKeys - an array of all Wide/Super Wide RHP keys */ -function getVisibleWideRHPKeys(allWideRHPKeys: string[]) { +function getVisibleRHPKeys(allSuperWideRHPKeys: string[], allWideRHPKeys: string[]): VisibleRHPKeys { if (!navigationRef.isReady()) { - return []; + return emptyRHPKeysState; } const rootState = navigationRef.getRootState(); if (!rootState) { - return []; + return emptyRHPKeysState; } const lastVisibleRHPRouteKey = getLastVisibleRHPRouteKey(rootState); const lastRHPRoute = rootState.routes.find((route) => route.key === lastVisibleRHPRouteKey); if (!lastRHPRoute) { - return []; + return emptyRHPKeysState; } - const lastRHPKeys = extractNavigationKeys(lastRHPRoute.state); - const currentKeys = allWideRHPKeys.filter((key) => lastRHPKeys.has(key)); + const superWideRHPIndex = lastRHPRoute.state?.routes.findLastIndex((route) => route?.key && allSuperWideRHPKeys.includes(route.key)) ?? -1; + const wideRHPIndex = lastRHPRoute.state?.routes.findLastIndex((route) => route?.key && allWideRHPKeys.includes(route.key)) ?? -1; + + let visibleRHPKeys; + if (superWideRHPIndex > -1) { + visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state?.routes.slice(superWideRHPIndex)); + } else if (wideRHPIndex > -1) { + visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state?.routes.slice(wideRHPIndex)); + } else { + visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state?.routes); + } - return currentKeys; + return { + visibleWideRHPRouteKeys: allWideRHPKeys.filter((key) => visibleRHPKeys.has(key)), + visibleSuperWideRHPRouteKeys: allSuperWideRHPKeys.filter((key) => visibleRHPKeys.has(key)), + }; } -export default getVisibleWideRHPKeys; +export default getVisibleRHPKeys; diff --git a/src/components/WideRHPContextProvider/index.native.tsx b/src/components/WideRHPContextProvider/index.native.tsx index 45c3ff387943b..b8773852e8ddd 100644 --- a/src/components/WideRHPContextProvider/index.native.tsx +++ b/src/components/WideRHPContextProvider/index.native.tsx @@ -3,11 +3,12 @@ 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 secondOverlayWideRHPProgress = new Animated.Value(0); +const secondOverlayRHPOnWideRHPProgress = new Animated.Value(0); +const secondOverlayRHPOnSuperWideRHPProgress = new Animated.Value(0); const thirdOverlayProgress = new Animated.Value(0); const animatedReceiptPaneRHPWidth = new Animated.Value(0); @@ -18,31 +19,25 @@ const modalStackOverlaySuperWideRHPPositionLeft = new Animated.Value(0); const modalStackOverlayWideRHPPositionLeft = new Animated.Value(0); const expandedRHPProgress = new Animated.Value(0); -const innerRHPProgress = 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}; } export default WideRHPContextProvider; -export type {WideRHPContextType}; export { animatedReceiptPaneRHPWidth, animatedSuperWideRHPWidth, animatedWideRHPWidth, expandedRHPProgress, - innerRHPProgress, modalStackOverlaySuperWideRHPPositionLeft, modalStackOverlayWideRHPPositionLeft, - secondOverlayProgress, + secondOverlayRHPOnSuperWideRHPProgress, + secondOverlayRHPOnWideRHPProgress, + secondOverlayWideRHPProgress, thirdOverlayProgress, WideRHPContext, - WIDE_RIGHT_MODALS, - SUPER_WIDE_RIGHT_MODALS, }; +export type {WideRHPContextType}; diff --git a/src/components/WideRHPContextProvider/index.tsx b/src/components/WideRHPContextProvider/index.tsx index ef20dca1b55f7..4920c89c79767 100644 --- a/src/components/WideRHPContextProvider/index.tsx +++ b/src/components/WideRHPContextProvider/index.tsx @@ -12,25 +12,22 @@ import calculateSuperWideRHPWidth from '@libs/Navigation/helpers/calculateSuperW import type {NavigationRoute} from '@libs/Navigation/types'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; -import SCREENS from '@src/SCREENS'; import type {Report} from '@src/types/onyx'; import defaultWideRHPContextValue from './default'; -import getIsWideRHPOpenedBelow from './getIsWideRHPOpenedBelow'; -import getVisibleWideRHPKeys from './getVisibleRHPRouteKeys'; +import getIsRHPDisplayedBelow from './getIsRHPDisplayedBelow'; +import getVisibleRHPKeys from './getVisibleRHPRouteKeys'; import type {WideRHPContextType} from './types'; import useShouldRenderOverlay from './useShouldRenderOverlay'; // 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 secondOverlayWideRHPProgress = new Animated.Value(0); +const secondOverlayRHPOnWideRHPProgress = new Animated.Value(0); +const secondOverlayRHPOnSuperWideRHPProgress = new Animated.Value(0); const thirdOverlayProgress = new Animated.Value(0); -// This array contains the names of 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]); - // The width of the left panel in Wide RHP where the receipt is displayed const receiptPaneRHPWidth = calculateReceiptPaneRHPWidth(Dimensions.get('window').width); @@ -98,57 +95,67 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { const [expenseReportIDs, setExpenseReportIDs] = useState>(new Set()); const [multiTransactionExpenseReportIDs, setMultiTransactionExpenseReportIDs] = useState>(new Set()); - // When closing Wide RHP, it is no longer the focused screen, this variable helps to determine the moment when Wide RHP is still visible on the screen but no longer focused - const [isWideRHPClosing, setIsWideRHPClosing] = useState(false); - const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: expenseReportSelector, canBeMissing: true}); - const focusedRoute = useRootNavigationState((state) => (state ? findFocusedRoute(state) : undefined)); + const {focusedRoute, focusedNavigator} = useRootNavigationState((state) => { + if (!state) { + return {focusedRoute: undefined, focusedNavigator: undefined}; + } + + return { + focusedRoute: findFocusedRoute(state), + focusedNavigator: state.routes.at(-1)?.name, + }; + }); const isWideRHPFocused = useMemo(() => { - return !!focusedRoute?.key && wideRHPRouteKeys.includes(focusedRoute.key); - }, [focusedRoute?.key, wideRHPRouteKeys]); + return !!focusedRoute?.key && allWideRHPRouteKeys.includes(focusedRoute.key); + }, [focusedRoute?.key, allWideRHPRouteKeys]); - // Whether Wide RHP is displayed below the currently displayed screen - const isWideRHPBelow = useMemo(() => getIsWideRHPOpenedBelow(focusedRoute, allWideRHPRouteKeys), [allWideRHPRouteKeys, focusedRoute]); + const isRHPFocused = focusedNavigator === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - // Whether Super Wide RHP is displayed below the currently displayed screen - const isSuperWideRHPBelow = useMemo(() => getIsWideRHPOpenedBelow(focusedRoute, allSuperWideRHPRouteKeys), [allSuperWideRHPRouteKeys, focusedRoute]); + // Whether Wide RHP is displayed below the currently displayed screen + const {isWideRHPBelow, isSuperWideRHPBelow} = getIsRHPDisplayedBelow(focusedRoute?.key, allSuperWideRHPRouteKeys, allWideRHPRouteKeys); // Updates the Wide RHP visible keys table from the all keys table - const syncWideRHPKeys = useCallback(() => { - setWideRHPRouteKeys(getVisibleWideRHPKeys(allWideRHPRouteKeys)); - }, [allWideRHPRouteKeys]); - - // Updates the Super Wide RHP visible keys table from the all keys table - const syncSuperWideRHPKeys = useCallback(() => { - setSuperWideRHPRouteKeys(getVisibleWideRHPKeys(allSuperWideRHPRouteKeys)); - }, [allSuperWideRHPRouteKeys]); + const syncRHPKeys = useCallback(() => { + const {visibleSuperWideRHPRouteKeys, visibleWideRHPRouteKeys} = getVisibleRHPKeys(allSuperWideRHPRouteKeys, allWideRHPRouteKeys); + setWideRHPRouteKeys(visibleWideRHPRouteKeys); + setSuperWideRHPRouteKeys(visibleSuperWideRHPRouteKeys); + }, [allSuperWideRHPRouteKeys, allWideRHPRouteKeys]); const clearWideRHPKeys = useCallback(() => { setWideRHPRouteKeys([]); setSuperWideRHPRouteKeys([]); }, []); - // Once we have updated the array of all Wide RHP keys, we should sync it with the array of RHP keys visible on the screen - useEffect(() => { - syncWideRHPKeys(); - }, [allWideRHPRouteKeys, syncWideRHPKeys]); - // Once we have updated the array of all Super Wide RHP keys, we should sync it with the array of RHP keys visible on the screen useEffect(() => { - syncSuperWideRHPKeys(); - }, [allSuperWideRHPRouteKeys, syncSuperWideRHPKeys]); + syncRHPKeys(); + }, [allSuperWideRHPRouteKeys, allWideRHPRouteKeys, syncRHPKeys]); + + /** + * Effect that manages the secondary overlay animation for single RHP displayed on Super Wide RHP and rendering state. + */ + const shouldRenderSecondaryOverlayForRHPOnSuperWideRHP = useShouldRenderOverlay( + isRHPFocused && isSuperWideRHPBelow && !isWideRHPBelow && !isWideRHPFocused, + secondOverlayRHPOnSuperWideRHPProgress, + ); + + /** + * Effect that manages the secondary overlay animation for single RHP displayed on Wide RHP and rendering state. + */ + const shouldRenderSecondaryOverlayForRHPOnWideRHP = useShouldRenderOverlay(isRHPFocused && isWideRHPBelow && !isWideRHPFocused, secondOverlayRHPOnWideRHPProgress); /** - * Effect that manages the secondary overlay animation and rendering state. + * Effect that manages the secondary overlay animation for Wide RHP displayed on Super Wide RHP and rendering state. */ - const shouldRenderSecondaryOverlay = useShouldRenderOverlay(isWideRHPBelow || isSuperWideRHPBelow, secondOverlayProgress); + const shouldRenderSecondaryOverlayForWideRHP = useShouldRenderOverlay(isRHPFocused && isSuperWideRHPBelow && (!!isWideRHPFocused || isWideRHPBelow), secondOverlayWideRHPProgress); /** * Effect that manages the tertiary overlay animation and rendering state. */ - const shouldRenderTertiaryOverlay = useShouldRenderOverlay(isWideRHPBelow && isSuperWideRHPBelow, thirdOverlayProgress); + const shouldRenderTertiaryOverlay = useShouldRenderOverlay(isRHPFocused && isWideRHPBelow && isSuperWideRHPBelow, thirdOverlayProgress); /** * Effect that shows/hides the expanded RHP progress based on the number of wide RHP routes. @@ -156,38 +163,46 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { 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) { expandedRHPProgress.setValue(1); - innerRHPProgress.setValue(0); } else { expandedRHPProgress.setValue(0); - innerRHPProgress.setValue(0); } }, [superWideRHPRouteKeys.length, wideRHPRouteKeys.length]); /** - * Adds a route to the wide RHP route keys list, enabling wide RHP display for that route. + * Removes a route from the super wide RHP route keys list, disabling wide RHP display for that route. */ - const showWideRHPVersion = useCallback((route: NavigationRoute) => showWideRHPRoute(route, setAllWideRHPRouteKeys), []); + const removeSuperWideRHPRouteKey = useCallback((route: NavigationRoute) => removeWideRHPRoute(route, setAllSuperWideRHPRouteKeys), []); /** - * Adds a route to the super wide RHP route keys list, enabling wide RHP display for that route. + * Removes a route from the wide RHP route keys list, disabling wide RHP display for that route. */ - const showSuperWideRHPVersion = useCallback((route: NavigationRoute) => showWideRHPRoute(route, setAllSuperWideRHPRouteKeys), []); + const removeWideRHPRouteKey = useCallback((route: NavigationRoute) => removeWideRHPRoute(route, setAllWideRHPRouteKeys), []); /** - * Removes a route from the super wide RHP route keys list, disabling wide RHP display for that route. + * Adds a route to the wide RHP route keys list, enabling wide RHP display for that route. */ - const removeSuperWideRHPRouteKey = useCallback((route: NavigationRoute) => removeWideRHPRoute(route, setAllSuperWideRHPRouteKeys), []); + const showWideRHPVersion = useCallback( + (route: NavigationRoute) => { + removeSuperWideRHPRouteKey(route); + showWideRHPRoute(route, setAllWideRHPRouteKeys); + }, + [removeSuperWideRHPRouteKey], + ); /** - * Removes a route from the wide RHP route keys list, disabling wide RHP display for that route. + * Adds a route to the super wide RHP route keys list, enabling super wide RHP display for that route. */ - const removeWideRHPRouteKey = useCallback((route: NavigationRoute) => removeWideRHPRoute(route, setAllWideRHPRouteKeys), []); + const showSuperWideRHPVersion = useCallback( + (route: NavigationRoute) => { + removeWideRHPRouteKey(route); + showWideRHPRoute(route, setAllSuperWideRHPRouteKeys); + }, + [removeWideRHPRouteKey], + ); /** * Marks a report ID as an expense report, adding it to the expense reports set. @@ -297,7 +312,9 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { showSuperWideRHPVersion, removeWideRHPRouteKey, removeSuperWideRHPRouteKey, - shouldRenderSecondaryOverlay, + shouldRenderSecondaryOverlayForRHPOnSuperWideRHP, + shouldRenderSecondaryOverlayForRHPOnWideRHP, + shouldRenderSecondaryOverlayForWideRHP, shouldRenderTertiaryOverlay, markReportIDAsExpense, markReportIDAsMultiTransactionExpense, @@ -305,10 +322,7 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { isReportIDMarkedAsExpense, isReportIDMarkedAsMultiTransactionExpense, isWideRHPFocused, - isWideRHPClosing, - setIsWideRHPClosing, - syncWideRHPKeys, - syncSuperWideRHPKeys, + syncRHPKeys, clearWideRHPKeys, }), [ @@ -318,7 +332,9 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { showSuperWideRHPVersion, removeWideRHPRouteKey, removeSuperWideRHPRouteKey, - shouldRenderSecondaryOverlay, + shouldRenderSecondaryOverlayForRHPOnSuperWideRHP, + shouldRenderSecondaryOverlayForRHPOnWideRHP, + shouldRenderSecondaryOverlayForWideRHP, shouldRenderTertiaryOverlay, markReportIDAsExpense, markReportIDAsMultiTransactionExpense, @@ -326,9 +342,7 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { isReportIDMarkedAsExpense, isReportIDMarkedAsMultiTransactionExpense, isWideRHPFocused, - isWideRHPClosing, - syncWideRHPKeys, - syncSuperWideRHPKeys, + syncRHPKeys, clearWideRHPKeys, ], ); @@ -343,12 +357,11 @@ export { animatedSuperWideRHPWidth, animatedWideRHPWidth, expandedRHPProgress, - innerRHPProgress, modalStackOverlaySuperWideRHPPositionLeft, modalStackOverlayWideRHPPositionLeft, - secondOverlayProgress, - SUPER_WIDE_RIGHT_MODALS, + secondOverlayWideRHPProgress, + secondOverlayRHPOnWideRHPProgress, + secondOverlayRHPOnSuperWideRHPProgress, thirdOverlayProgress, - WIDE_RIGHT_MODALS, WideRHPContext, }; diff --git a/src/components/WideRHPContextProvider/types.ts b/src/components/WideRHPContextProvider/types.ts index 9925c5a4aa1fd..76f6d2df7adce 100644 --- a/src/components/WideRHPContextProvider/types.ts +++ b/src/components/WideRHPContextProvider/types.ts @@ -7,8 +7,14 @@ type WideRHPContextType = { // Route keys of screens that should be displayed in super wide format superWideRHPRouteKeys: string[]; - // If the secondary overlay should be rendered. This value takes into account the delay of closing transition. - shouldRenderSecondaryOverlay: boolean; + // If the secondary overlay for wide RHP on super wide RHP should be rendered. This value takes into account the delay of closing transition. + shouldRenderSecondaryOverlayForWideRHP: boolean; + + // If the secondary overlay for single RHP on wide RHP should be rendered. This value takes into account the delay of closing transition. + shouldRenderSecondaryOverlayForRHPOnWideRHP: boolean; + + // If the secondary overlay for single RHP on super wide RHP should be rendered. This value takes into account the delay of closing transition. + shouldRenderSecondaryOverlayForRHPOnSuperWideRHP: boolean; // If the tertiary overlay should be rendered. This value takes into account the delay of closing transition. shouldRenderTertiaryOverlay: boolean; @@ -43,17 +49,8 @@ type WideRHPContextType = { // 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; + // Sync super wide and wide RHP keys with the visible RHP screens + syncRHPKeys: () => void; // Clear the arrays of wide and super wide rhp keys clearWideRHPKeys: () => void; diff --git a/src/components/WideRHPContextProvider/useShowSuperWideRHPVersion/index.ts b/src/components/WideRHPContextProvider/useShowSuperWideRHPVersion/index.ts index bce4d67acfec1..f2b7301f36d02 100644 --- a/src/components/WideRHPContextProvider/useShowSuperWideRHPVersion/index.ts +++ b/src/components/WideRHPContextProvider/useShowSuperWideRHPVersion/index.ts @@ -14,8 +14,15 @@ import {WideRHPContext} from '..'; function useShowSuperWideRHPVersion(condition: boolean) { const route = useRoute(); const reportID = route.params && 'reportID' in route.params && typeof route.params.reportID === 'string' ? route.params.reportID : ''; - const {showWideRHPVersion, showSuperWideRHPVersion, removeWideRHPRouteKey, removeSuperWideRHPRouteKey, isReportIDMarkedAsExpense, isReportIDMarkedAsMultiTransactionExpense} = - useContext(WideRHPContext); + const { + showWideRHPVersion, + showSuperWideRHPVersion, + removeWideRHPRouteKey, + unmarkReportIDAsMultiTransactionExpense, + removeSuperWideRHPRouteKey, + isReportIDMarkedAsExpense, + isReportIDMarkedAsMultiTransactionExpense, + } = useContext(WideRHPContext); const onSuperWideRHPClose = useCallback(() => { // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -33,12 +40,28 @@ function useShowSuperWideRHPVersion(condition: boolean) { */ useEffect(() => { // Check if we should show wide RHP based on condition OR if reportID is in optimistic set - if (condition || (reportID && isReportIDMarkedAsMultiTransactionExpense(reportID))) { + const isReportMultiTransactionExpense = reportID && isReportIDMarkedAsMultiTransactionExpense(reportID); + + if (condition || isReportMultiTransactionExpense) { + if (condition && isReportMultiTransactionExpense) { + unmarkReportIDAsMultiTransactionExpense(reportID); + } + showSuperWideRHPVersion(route); return; } + showWideRHPVersion(route); - }, [condition, reportID, isReportIDMarkedAsExpense, route, showWideRHPVersion, showSuperWideRHPVersion, isReportIDMarkedAsMultiTransactionExpense]); + }, [ + condition, + reportID, + isReportIDMarkedAsExpense, + route, + showWideRHPVersion, + showSuperWideRHPVersion, + isReportIDMarkedAsMultiTransactionExpense, + unmarkReportIDAsMultiTransactionExpense, + ]); } export default useShowSuperWideRHPVersion; diff --git a/src/components/WideRHPContextProvider/useShowWideRHPVersion/index.ts b/src/components/WideRHPContextProvider/useShowWideRHPVersion/index.ts index f018f21718452..27a6ce16fdad4 100644 --- a/src/components/WideRHPContextProvider/useShowWideRHPVersion/index.ts +++ b/src/components/WideRHPContextProvider/useShowWideRHPVersion/index.ts @@ -14,25 +14,18 @@ import {WideRHPContext} from '..'; function useShowWideRHPVersion(condition: boolean) { const route = useRoute(); const reportID = route.params && 'reportID' in route.params && typeof route.params.reportID === 'string' ? route.params.reportID : ''; - const {showWideRHPVersion, removeWideRHPRouteKey, isReportIDMarkedAsExpense, setIsWideRHPClosing} = useContext(WideRHPContext); - - // When switching between reports using the arrow keys, the transition is completed when the new report screen is mounted. - useEffect(() => { - setIsWideRHPClosing(false); - }, [setIsWideRHPClosing]); + const {showWideRHPVersion, removeWideRHPRouteKey, isReportIDMarkedAsExpense} = useContext(WideRHPContext); // beforeRemove event is not called when closing nested Wide RHP using the browser back button. // This hook removes the route key from the array in the following case. useEffect(() => () => removeWideRHPRouteKey(route), [removeWideRHPRouteKey, route]); const onWideRHPClose = useCallback(() => { - setIsWideRHPClosing(true); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { removeWideRHPRouteKey(route); - setIsWideRHPClosing(false); }); - }, [removeWideRHPRouteKey, route, setIsWideRHPClosing]); + }, [removeWideRHPRouteKey, route]); /** * Effect that sets up cleanup when the screen is about to be removed. @@ -47,7 +40,6 @@ function useShowWideRHPVersion(condition: boolean) { useEffect(() => { // Check if we should show wide RHP based on condition OR if reportID is in optimistic set const shouldShow = condition || (reportID && isReportIDMarkedAsExpense(reportID)); - if (!shouldShow) { return; } diff --git a/src/components/WideRHPOverlayWrapper/SecondaryOverlay.tsx b/src/components/WideRHPOverlayWrapper/SecondaryOverlay.tsx new file mode 100644 index 0000000000000..f971db206e199 --- /dev/null +++ b/src/components/WideRHPOverlayWrapper/SecondaryOverlay.tsx @@ -0,0 +1,68 @@ +import {useRoute} from '@react-navigation/native'; +import React, {useContext} from 'react'; +import { + animatedReceiptPaneRHPWidth, + modalStackOverlaySuperWideRHPPositionLeft, + modalStackOverlayWideRHPPositionLeft, + secondOverlayRHPOnSuperWideRHPProgress, + secondOverlayRHPOnWideRHPProgress, + secondOverlayWideRHPProgress, + WideRHPContext, +} from '@components/WideRHPContextProvider'; +import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; + +export default function SecondaryOverlay() { + const {shouldRenderSecondaryOverlayForRHPOnSuperWideRHP, shouldRenderSecondaryOverlayForRHPOnWideRHP, shouldRenderSecondaryOverlayForWideRHP, superWideRHPRouteKeys, wideRHPRouteKeys} = + useContext(WideRHPContext); + + const route = useRoute(); + + const isWide = !!route?.key && wideRHPRouteKeys.includes(route.key); + const isSuperWide = !!route?.key && superWideRHPRouteKeys.includes(route.key); + + const isRHPDisplayedOnWideRHP = shouldRenderSecondaryOverlayForRHPOnWideRHP && isWide; + const isRHPDisplayedOnSuperWideRHP = shouldRenderSecondaryOverlayForRHPOnSuperWideRHP && isSuperWide; + const isWideRHPDisplayedOnSuperWideRHP = shouldRenderSecondaryOverlayForWideRHP && isSuperWide; + + /** + * 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. + * 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. + * Three cases were described for the secondary overlay: + * 1. Single RHP is displayed on Wide RHP + * 2. Single RHP is displayed on Super Wide RHP + * 3. Wide RHP is displayed on Super Wide RHP route. + * */ + if (isRHPDisplayedOnWideRHP) { + return ( + + ); + } + + if (isWideRHPDisplayedOnSuperWideRHP) { + return ( + + ); + } + + if (isRHPDisplayedOnSuperWideRHP) { + return ( + + ); + } + + return null; +} diff --git a/src/components/WideRHPOverlayWrapper/TertiaryOverlay.tsx b/src/components/WideRHPOverlayWrapper/TertiaryOverlay.tsx new file mode 100644 index 0000000000000..7cfae8ff62d16 --- /dev/null +++ b/src/components/WideRHPOverlayWrapper/TertiaryOverlay.tsx @@ -0,0 +1,24 @@ +import {useRoute} from '@react-navigation/native'; +import React, {useContext} from 'react'; +import {modalStackOverlaySuperWideRHPPositionLeft, thirdOverlayProgress, WideRHPContext} from '@components/WideRHPContextProvider'; +import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; + +export default function TertiaryOverlay() { + const {shouldRenderTertiaryOverlay, wideRHPRouteKeys} = useContext(WideRHPContext); + const route = useRoute(); + + const isWide = route?.key && wideRHPRouteKeys.includes(route.key); + + // This overlay is used to cover the space under the narrower RHP screen when more than one RHP width is displayed on the screen + // There is 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. + if (isWide && shouldRenderTertiaryOverlay) { + return ( + + ); + } + + return null; +} diff --git a/src/components/WideRHPOverlayWrapper/index.native.ts b/src/components/WideRHPOverlayWrapper/index.native.ts new file mode 100644 index 0000000000000..43803a11607dd --- /dev/null +++ b/src/components/WideRHPOverlayWrapper/index.native.ts @@ -0,0 +1,8 @@ +type WideRHPOverlayWrapperProps = { + children: React.ReactNode; +}; + +// Overlays aren't displayed on native platforms. +export default function WideRHPOverlayWrapper({children}: WideRHPOverlayWrapperProps) { + return children; +} diff --git a/src/components/WideRHPOverlayWrapper/index.tsx b/src/components/WideRHPOverlayWrapper/index.tsx new file mode 100644 index 0000000000000..8996426adb504 --- /dev/null +++ b/src/components/WideRHPOverlayWrapper/index.tsx @@ -0,0 +1,50 @@ +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useContext} from 'react'; +import {WideRHPContext} from '@components/WideRHPContextProvider'; +import {navigationRef} from '@libs/Navigation/Navigation'; +import NAVIGATORS from '@src/NAVIGATORS'; +import SecondaryOverlay from './SecondaryOverlay'; +import TertiaryOverlay from './TertiaryOverlay'; + +type WideRHPOverlayWrapperProps = { + children: React.ReactNode; + shouldWrap?: boolean; +}; + +// This overlay is used to cover the space under the narrower RHP screen when more than one RHP width is displayed on the screen. +export default function WideRHPOverlayWrapper({children, shouldWrap = true}: WideRHPOverlayWrapperProps) { + const {syncRHPKeys} = useContext(WideRHPContext); + + // This hook handles the case when a wider RHP is displayed above a narrower one. + // In this situation, we need to synchronize the keys, as superWideRHPKeys and wideRHPKeys store the keys of the screens that are visible. + useFocusEffect( + useCallback( + () => () => { + if (!shouldWrap) { + return; + } + + // Synchronization after RHP unmount is handled in RightModalNavigator.tsx. + const isRHPOpened = navigationRef?.getRootState()?.routes?.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; + if (!isRHPOpened) { + return; + } + + syncRHPKeys(); + }, + [shouldWrap, syncRHPKeys], + ), + ); + + if (!shouldWrap) { + return children; + } + + return ( + <> + {children} + + + + ); +} diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index e840e2e874098..3d29acd810246 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -155,7 +155,8 @@ function AuthScreens() { const {initialURL, isAuthenticatedAtStartup, setIsAuthenticatedAtStartup} = useContext(InitialURLContext); const modalCardStyleInterpolator = useModalCardStyleInterpolator(); const archivedReportsIdSet = useArchivedReportsIdSet(); - const {shouldRenderSecondaryOverlay, shouldRenderTertiaryOverlay} = useContext(WideRHPContext); + const {shouldRenderSecondaryOverlayForWideRHP, shouldRenderSecondaryOverlayForRHPOnWideRHP, shouldRenderSecondaryOverlayForRHPOnSuperWideRHP, shouldRenderTertiaryOverlay} = + useContext(WideRHPContext); // State to track whether the delegator's authentication is completed before displaying data const [isDelegatorFromOldDotIsReady, setIsDelegatorFromOldDotIsReady] = useState(false); @@ -342,13 +343,23 @@ function AuthScreens() { return; } - if (shouldRenderSecondaryOverlay) { - Navigation.dismissToFirstRHP(); + if (shouldRenderSecondaryOverlayForWideRHP) { + Navigation.closeRHPFlow(); + return; + } + + if (shouldRenderSecondaryOverlayForRHPOnSuperWideRHP) { + Navigation.dismissToSuperWideRHP(); + return; + } + + if (shouldRenderSecondaryOverlayForRHPOnWideRHP) { + Navigation.dismissToPreviousRHP(); return; } if (shouldRenderTertiaryOverlay) { - Navigation.dismissToSecondRHP(); + Navigation.dismissToPreviousRHP(); return; } @@ -360,7 +371,14 @@ function AuthScreens() { true, ); return () => unsubscribeEscapeKey(); - }, [modal?.disableDismissOnEscape, modal?.willAlertModalBecomeVisible, shouldRenderSecondaryOverlay, shouldRenderTertiaryOverlay]); + }, [ + modal?.disableDismissOnEscape, + modal?.willAlertModalBecomeVisible, + shouldRenderSecondaryOverlayForWideRHP, + shouldRenderSecondaryOverlayForRHPOnWideRHP, + shouldRenderSecondaryOverlayForRHPOnSuperWideRHP, + shouldRenderTertiaryOverlay, + ]); // 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 85642f25d618e..c55381c4d357e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -1,18 +1,8 @@ -import {useIsFocused, useRoute} from '@react-navigation/native'; import type {ParamListBase} from '@react-navigation/routers'; -import React, {useCallback, useContext} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; -import { - animatedReceiptPaneRHPWidth, - modalStackOverlaySuperWideRHPPositionLeft, - modalStackOverlayWideRHPPositionLeft, - secondOverlayProgress, - thirdOverlayProgress, - WideRHPContext, -} from '@components/WideRHPContextProvider'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation'; import type {PlatformStackNavigationOptions} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -41,12 +31,12 @@ import type { ReportDetailsNavigatorParamList, ReportSettingsNavigatorParamList, ReportVerifyAccountNavigatorParamList, + RestrictedActionParamList, RoomMembersNavigatorParamList, ScheduleCallParamList, SearchAdvancedFiltersParamList, SearchColumnsParamList, SearchReportActionsParamList, - SearchReportParamList, SearchSavedSearchParamList, SettingsNavigatorParamList, ShareNavigatorParamList, @@ -74,9 +64,6 @@ const OPTIONS_PER_SCREEN: Partial [SCREENS.SETTINGS.MERGE_ACCOUNTS.MERGE_RESULT]: { animationTypeForReplace: 'push', }, - [SCREENS.SEARCH.REPORT_RHP]: { - animation: Animations.NONE, - }, [SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: { animation: Animations.NONE, }, @@ -100,14 +87,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. * @@ -119,10 +98,6 @@ function createModalStackNavigator(screens: Scr function ModalStack() { const styles = useThemeStyles(); const screenOptions = useModalStackScreenOptions(); - const {shouldRenderSecondaryOverlay, shouldRenderTertiaryOverlay, isWideRHPFocused, superWideRHPRouteKeys, isWideRHPClosing} = 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 @@ -139,11 +114,6 @@ function createModalStackNavigator(screens: Scr [screenOptions], ); - const isRHPDisplayedOnWideRHP = - !isSmallScreenWidth && !isWideRHPFocused && !isWideRHPClosing && shouldRenderSecondaryOverlay && (isSuperWideRHPRouteName(route.name) || isWideRHPRouteName(route.name)); - - const isWideRHPDisplayedOnSuperWideRHP = !isSmallScreenWidth && !isFocused && !!isWideRHPFocused && shouldRenderSecondaryOverlay && isSuperWideRHPRouteName(route.name); - return ( // This container is necessary to hide card translation during transition. Without it the user would see un-clipped cards. @@ -158,34 +128,6 @@ 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 ? modalStackOverlaySuperWideRHPPositionLeft : animatedReceiptPaneRHPWidth} - /> - ) : null} - {isWideRHPDisplayedOnSuperWideRHP ? ( - - ) : null} - {!isSmallScreenWidth && shouldRenderTertiaryOverlay && isWideRHPRouteName(route.name) ? ( - - ) : null} ); } @@ -919,11 +861,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, -}); - 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, @@ -972,7 +909,7 @@ const SearchSavedSearchModalStackNavigator = createModalStackNavigator require('../../../../pages/Search/SavedSearchRenamePage').default, }); -const RestrictedActionModalStackNavigator = createModalStackNavigator({ +const RestrictedActionModalStackNavigator = createModalStackNavigator({ [SCREENS.RESTRICTED_ACTION_ROOT]: () => require('../../../../pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage').default, }); @@ -1023,52 +960,51 @@ const WorkspacesDomainModalStackNavigator = createModalStackNavigator}) => PlatformStackNavigationOptions>( ({route}) => { - let cardStyleInterpolator; + let cardStyleInterpolator = CardStyleInterpolators.forHorizontalIOS; - if (superWideRHPRouteKeys.includes(route.key) && !isSmallScreenWidth) { - cardStyleInterpolator = enhanceCardStyleInterpolator(CardStyleInterpolators.forHorizontalIOS, { - cardStyle: styles.superWideRHPExtendedCardInterpolatorStyles, - }); - } else 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, { - cardStyle: styles.wideRHPExtendedCardInterpolatorStyles, - }); - } else { - cardStyleInterpolator = CardStyleInterpolators.forHorizontalIOS; + if (!isSmallScreenWidth) { + if (superWideRHPRouteKeys.includes(route.key)) { + cardStyleInterpolator = enhanceCardStyleInterpolator(CardStyleInterpolators.forHorizontalIOS, { + cardStyle: styles.superWideRHPExtendedCardInterpolatorStyles, + }); + } else if (wideRHPRouteKeys.includes(route.key)) { + cardStyleInterpolator = enhanceCardStyleInterpolator(CardStyleInterpolators.forHorizontalIOS, { + cardStyle: styles.wideRHPExtendedCardInterpolatorStyles, + }); + // single RHPs displayed above the wide RHP need to be positioned + } else if (superWideRHPRouteKeys.length > 0 || wideRHPRouteKeys.length > 0) { + cardStyleInterpolator = enhanceCardStyleInterpolator(CardStyleInterpolators.forHorizontalIOS, { + cardStyle: styles.singleRHPExtendedCardInterpolatorStyles, + }); + } } return { @@ -53,4 +55,4 @@ function useModalStackScreenOptions() { ); } -export default useModalStackScreenOptions; +export default useWideModalStackScreenOptions; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index f3ffce43dbb13..253b740123388 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -1,11 +1,18 @@ import type {NavigatorScreenParams} from '@react-navigation/native'; import {useFocusEffect} from '@react-navigation/native'; -import type {StackCardInterpolationProps} from '@react-navigation/stack'; import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react'; // eslint-disable-next-line no-restricted-imports import {Animated, DeviceEventEmitter, InteractionManager} from 'react-native'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; -import {animatedWideRHPWidth, expandedRHPProgress, innerRHPProgress, secondOverlayProgress, thirdOverlayProgress, WideRHPContext} from '@components/WideRHPContextProvider'; +import { + animatedWideRHPWidth, + expandedRHPProgress, + secondOverlayRHPOnSuperWideRHPProgress, + secondOverlayRHPOnWideRHPProgress, + secondOverlayWideRHPProgress, + thirdOverlayProgress, + WideRHPContext, +} from '@components/WideRHPContextProvider'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -13,7 +20,7 @@ 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 useModalStackScreenOptions from '@libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions'; import useRHPScreenOptions from '@libs/Navigation/AppNavigator/useRHPScreenOptions'; import calculateReceiptPaneRHPWidth from '@libs/Navigation/helpers/calculateReceiptPaneRHPWidth'; import calculateSuperWideRHPWidth from '@libs/Navigation/helpers/calculateSuperWideRHPWidth'; @@ -26,6 +33,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; +import type ReactComponentModule from '@src/types/utils/ReactComponentModule'; import {NarrowPaneContextProvider} from './NarrowPaneContext'; import Overlay from './Overlay'; @@ -36,15 +44,51 @@ const Stack = createPlatformStackNavigator const singleRHPWidth = variables.sideBarWidth; const getWideRHPWidth = (windowWidth: number) => variables.sideBarWidth + calculateReceiptPaneRHPWidth(windowWidth); +function SecondaryOverlay() { + const {shouldRenderSecondaryOverlayForWideRHP, shouldRenderSecondaryOverlayForRHPOnWideRHP, shouldRenderSecondaryOverlayForRHPOnSuperWideRHP} = useContext(WideRHPContext); + + if (shouldRenderSecondaryOverlayForWideRHP) { + return ( + Navigation.closeRHPFlow()} + /> + ); + } + + if (shouldRenderSecondaryOverlayForRHPOnWideRHP) { + return ( + + ); + } + + if (shouldRenderSecondaryOverlayForRHPOnSuperWideRHP) { + return ( + + ); + } + + return null; +} + +const loadReportScreen = () => require('../../../../pages/home/ReportScreen').default; + function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const isExecutingRef = useRef(false); const screenOptions = useRHPScreenOptions(); - const {shouldRenderSecondaryOverlay, isWideRHPFocused, shouldRenderTertiaryOverlay, isWideRHPClosing, clearWideRHPKeys, syncWideRHPKeys, syncSuperWideRHPKeys} = - useContext(WideRHPContext); + const {shouldRenderTertiaryOverlay, clearWideRHPKeys, syncRHPKeys} = useContext(WideRHPContext); const {windowWidth} = useWindowDimensions(); - const modalCardStyleInterpolator = useModalCardStyleInterpolator(); + const modalStackScreenOptions = useModalStackScreenOptions(); const styles = useThemeStyles(); const animatedWidth = expandedRHPProgress.interpolate({ @@ -104,11 +148,12 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { useFocusEffect( useCallback(() => { - syncWideRHPKeys(); - syncSuperWideRHPKeys(); + // When we open a second RightModalNavigator while the previous one is covered by a fullscreen navigator, we need to synchronize the keys. + syncRHPKeys(); + // Super wide and wide route keys have to be cleared when the RightModalNavigator is not closed and a new navigator is opened above it. return () => clearWideRHPKeysAfterTabChanged(); - }, [syncWideRHPKeys, syncSuperWideRHPKeys, clearWideRHPKeysAfterTabChanged]), + }, [syncRHPKeys, clearWideRHPKeysAfterTabChanged]), ); useEffect(() => () => DeviceEventEmitter.emit(CONST.MODAL_EVENTS.CLOSED), []); @@ -291,23 +336,8 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { /> - // 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), - }), - }, - }} + getComponent={loadReportScreen} + options={modalStackScreenOptions} /> - )} - {shouldRenderSecondaryOverlay && !shouldUseNarrowLayout && !!isWideRHPFocused && ( - - )} - {shouldRenderTertiaryOverlay && !shouldUseNarrowLayout && ( + {!shouldUseNarrowLayout && } + {!shouldUseNarrowLayout && shouldRenderTertiaryOverlay && ( )} diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index d53578f1b9916..7c894c9e2525e 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -8,7 +8,7 @@ import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {Writable} from 'type-fest'; -import {SUPER_WIDE_RIGHT_MODALS, WIDE_RIGHT_MODALS} from '@components/WideRHPContextProvider'; +import {ALL_WIDE_RIGHT_MODALS, SUPER_WIDE_RIGHT_MODALS} from '@components/WideRHPContextProvider/WIDE_RIGHT_MODALS'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import Log from '@libs/Log'; import {shallowCompare} from '@libs/ObjectUtils'; @@ -741,16 +741,18 @@ function dismissToModalStack(modalStackNames: Set) { /** * Dismiss top layer modal and go back to the Wide/Super Wide RHP. */ -function dismissToFirstRHP() { - const wideOrSuperWideModalStackNames = new Set([...SUPER_WIDE_RIGHT_MODALS, ...WIDE_RIGHT_MODALS]); - return dismissToModalStack(wideOrSuperWideModalStackNames); +function dismissToPreviousRHP() { + return dismissToModalStack(ALL_WIDE_RIGHT_MODALS); } -/** - * Dismiss top layer modal and go back to the Wide RHP. - */ -function dismissToSecondRHP() { - return dismissToModalStack(WIDE_RIGHT_MODALS); +function dismissToSuperWideRHP() { + // On narrow layouts (mobile), Super Wide RHP doesn't exist, so just dismiss the modal completely + if (getIsNarrowLayout()) { + dismissModal(); + return; + } + // On wide layouts, dismiss back to the Super Wide RHP modal stack + return dismissToModalStack(SUPER_WIDE_RIGHT_MODALS); } export default { @@ -788,8 +790,8 @@ export default { isOnboardingFlow, clearPreloadedRoutes, isValidateLoginFlow, - dismissToFirstRHP, - dismissToSecondRHP, + dismissToPreviousRHP, + dismissToSuperWideRHP, }; export {navigationRef}; diff --git a/src/libs/Navigation/helpers/extractNavigationKeys.ts b/src/libs/Navigation/helpers/extractNavigationKeys.ts index 0f78e2b6fe9e1..27b7945c38cd7 100644 --- a/src/libs/Navigation/helpers/extractNavigationKeys.ts +++ b/src/libs/Navigation/helpers/extractNavigationKeys.ts @@ -1,19 +1,19 @@ -import type {NavigationState, PartialState} from '@react-navigation/native'; +import type {NavigationRoute} from '@libs/Navigation/types'; /** - * Utility function that extracts all unique navigation keys from a React Navigation state. - * Recursively traverses the navigation state tree and collects all route keys. + * Utility function that extracts all unique navigation keys from an array of routes. + * Recursively traverses the routes and collects all route keys. * - * @param state - The React Navigation state (can be partial or complete) + * @param routes - array of routes for key extraction * @returns Set of unique route keys found in the navigation state */ -function extractNavigationKeys(state: NavigationState | PartialState | undefined): Set { - if (!state || !state.routes) { +function extractNavigationKeys(routes: NavigationRoute[] | undefined): Set { + if (!routes) { return new Set(); } const keys = new Set(); - const routesToProcess = [...state.routes]; + const routesToProcess = [...routes]; while (routesToProcess.length > 0) { const route = routesToProcess.pop(); diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts index 7c034c8b4b1e1..0bdf92563ae40 100644 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts @@ -8,7 +8,6 @@ const SEARCH_TO_RHP: Partial['config'] = { [SCREENS.SEARCH.COLUMNS_RHP]: ROUTES.SEARCH_COLUMNS, }, }, - [SCREENS.RIGHT_MODAL.SEARCH_REPORT]: { - screens: { - [SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT.route, - }, - }, + [SCREENS.RIGHT_MODAL.SEARCH_REPORT]: ROUTES.SEARCH_REPORT.route, [SCREENS.RIGHT_MODAL.SEARCH_REPORT_ACTIONS]: { screens: { [SCREENS.SEARCH.ROOT_VERIFY_ACCOUNT]: ROUTES.SEARCH_ROOT_VERIFY_ACCOUNT, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 2974734b708c6..c578a83c57ed9 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -2181,7 +2181,6 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.TRANSACTION_DUPLICATE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TRAVEL]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_REPORT_ACTIONS]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.SEARCH_REPORT]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_ADVANCED_FILTERS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_SAVED_SEARCH]: NavigatorScreenParams; @@ -2195,6 +2194,12 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.MERGE_TRANSACTION]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.DOMAIN]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_COLUMNS]: NavigatorScreenParams; + [SCREENS.RIGHT_MODAL.SEARCH_REPORT]: { + 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 TravelNavigatorParamList = { @@ -2699,15 +2704,6 @@ 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 SearchFullscreenNavigatorParamList = { [SCREENS.SEARCH.ROOT]: { q: SearchQueryString; @@ -2902,7 +2898,6 @@ export type { RootNavigatorParamList, SearchAdvancedFiltersParamList, SearchReportActionsParamList, - SearchReportParamList, SearchSavedSearchParamList, SearchFullscreenNavigatorParamList, SettingsNavigatorParamList, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d3b00c3b75f71..35f3c973bf684 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -10181,7 +10181,7 @@ function navigateToLinkedReportAction(ancestor: Ancestor, isInNarrowPaneModal: b ROUTES.SEARCH_REPORT.getRoute({ reportID: ancestor.report.reportID, reportActionID: ancestor.reportAction.reportActionID, - backTo: SCREENS.SEARCH.REPORT_RHP, + backTo: SCREENS.RIGHT_MODAL.SEARCH_REPORT, }), ); return; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 52176f7d5e42c..0ec27f933dd6b 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -226,7 +226,7 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, const isLoading = !report?.reportID || !title; const isParentReportLoading = !!report?.parentReportID && !parentReport; - const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; + const isReportInRHP = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, {canBeMissing: true}); const isChatUsedForOnboarding = isChatUsedForOnboardingReportUtils(report, onboardingPurposeSelected); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 65abac82624c5..1793415a1a835 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, useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useContext, 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,9 @@ import MoneyRequestReceiptView from '@components/ReportActionItem/MoneyRequestRe import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; +import {WideRHPContext} from '@components/WideRHPContextProvider'; import useShowWideRHPVersion from '@components/WideRHPContextProvider/useShowWideRHPVersion'; +import WideRHPOverlayWrapper from '@components/WideRHPOverlayWrapper'; import useAppFocusEvent from '@hooks/useAppFocusEvent'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useDeepCompareRef from '@hooks/useDeepCompareRef'; @@ -87,7 +89,7 @@ import { } from '@libs/ReportUtils'; import {cancelSpan} from '@libs/telemetry/activeSpans'; import {isNumeric} from '@libs/ValidationUtils'; -import type {ReportsSplitNavigatorParamList, SearchReportParamList} from '@navigation/types'; +import type {ReportsSplitNavigatorParamList, RightModalNavigatorParamList} from '@navigation/types'; import {setShouldShowComposeInput} from '@userActions/Composer'; import { clearDeleteTransactionNavigateBackUrl, @@ -115,7 +117,7 @@ import {ActionListContext} from './ReportScreenContext'; type ReportScreenNavigationProps = | PlatformStackScreenProps - | PlatformStackScreenProps; + | PlatformStackScreenProps; type ReportScreenProps = ReportScreenNavigationProps; @@ -167,6 +169,10 @@ 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 isWideRHPOpened = wideRHPRouteKeys.length > 0 || superWideRHPRouteKeys.length > 0; const currentReportIDValue = useCurrentReportID(); const [isComposerFullSize = false] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${reportIDFromRoute}`, {canBeMissing: true}); @@ -372,7 +378,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const backTo = route?.params?.backTo as string; const onBackButtonPress = useCallback( (prioritizeBackTo = false) => { - if (backTo === SCREENS.SEARCH.REPORT_RHP) { + if (backTo === SCREENS.RIGHT_MODAL.SEARCH_REPORT || isDisplayedInWidePaneModal || isWideRHPOpened) { Navigation.goBack(); return; } @@ -394,7 +400,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } Navigation.goBack(); }, - [isInNarrowPaneModal, backTo], + [backTo, isDisplayedInWidePaneModal, isWideRHPOpened, isInNarrowPaneModal], ); let headerView = ( @@ -866,7 +872,12 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // for legacy transaction that doesn't have IOU action useEffect(() => { // Skip if already created, coming from Search page, or thread already exists - if (hasCreatedLegacyThreadRef.current || route.name === SCREENS.SEARCH.REPORT_RHP || transactionThreadReport || (transactionThreadReportID && transactionThreadReportID !== '0')) { + if ( + hasCreatedLegacyThreadRef.current || + route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT || + transactionThreadReport || + (transactionThreadReportID && transactionThreadReportID !== '0') + ) { return; } @@ -910,7 +921,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // WideRHP should be visible only on wide layout when report is opened in RHP and contains only one expense. // This view is only available for reports of type CONST.REPORT.TYPE.EXPENSE or CONST.REPORT.TYPE.IOU. const shouldShowWideRHP = - route.name === SCREENS.SEARCH.REPORT_RHP && + route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT && !isSmallScreenWidth && !shouldDisplayMoneyRequestActionsList && (isTransactionThread(parentReportAction) || @@ -930,113 +941,116 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } return ( - - - - + + + - - - {headerView} - - {!!accountManagerReportID && isConciergeChatReport(report) && isBannerVisible && ( - - )} - - {shouldShowWideRHP && ( - - - - - - )} - + + - {(!report || shouldWaitForTransactions) && } - {!!report && !shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( - - ) : null} - {!!report && shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( - - ) : null} - {isCurrentReportLoadedFromOnyx ? ( - - ) : null} + {headerView} + + {!!accountManagerReportID && isConciergeChatReport(report) && isBannerVisible && ( + + )} + + {shouldShowWideRHP && ( + + + + + + )} + + {(!report || shouldWaitForTransactions) && } + {!!report && !shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( + + ) : null} + {!!report && shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( + + ) : null} + {isCurrentReportLoadedFromOnyx ? ( + + ) : null} + - - - - - - - + + + + + + + ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index b6e395843f267..cff7ad81b677b 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5421,19 +5421,26 @@ const staticStyles = (theme: ThemeColors) => }, wideRHPExtendedCardInterpolatorStyles: { - position: Platform.OS === 'web' ? 'fixed' : 'absolute', + position: 'absolute', height: '100%', right: 0, width: animatedWideRHPWidth, }, superWideRHPExtendedCardInterpolatorStyles: { - position: Platform.OS === 'web' ? 'fixed' : 'absolute', + position: 'absolute', height: '100%', right: 0, width: animatedSuperWideRHPWidth, }, + singleRHPExtendedCardInterpolatorStyles: { + position: 'absolute', + height: '100%', + right: 0, + width: variables.sideBarWidth, + }, + flexibleHeight: { height: 'auto', minHeight: 200, From a96e6de7b1f7f0682dad990d2eef7f7d6ee977e1 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Dec 2025 11:49:41 +0100 Subject: [PATCH 2/5] Fix issue #77587 - Web - Expense - + menu appears in the middle --- .../AttachmentPickerWithMenuItems.tsx | 24 +++++++++++++++---- src/styles/index.ts | 7 ------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index 3606bc71d71d3..87304d1dfc7f0 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -1,6 +1,6 @@ import {useIsFocused} from '@react-navigation/native'; import {accountIDSelector} from '@selectors/Session'; -import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import AttachmentPicker from '@components/AttachmentPicker'; @@ -19,13 +19,13 @@ import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; +import usePopoverPosition from '@hooks/usePopoverPosition'; import usePreferredPolicy from '@hooks/usePreferredPolicy'; import usePrevious from '@hooks/usePrevious'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import {isSafari} from '@libs/Browser'; import getIconForAction from '@libs/getIconForAction'; import Navigation from '@libs/Navigation/Navigation'; @@ -49,6 +49,7 @@ import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {AnchorPosition} from '@src/styles'; import type * as OnyxTypes from '@src/types/onyx'; import type {FileObject} from '@src/types/utils/Attachment'; import getEmptyArray from '@src/types/utils/getEmptyArray'; @@ -142,8 +143,9 @@ function AttachmentPickerWithMenuItems({ const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {windowHeight, windowWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {calculatePopoverPosition} = usePopoverPosition(); + const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {canBeMissing: true}); const [lastDistanceExpenseType] = useOnyx(ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE, {canBeMissing: true}); @@ -351,6 +353,20 @@ function AttachmentPickerWithMenuItems({ setMenuVisibility(false); }, [didScreenBecomeInactive, isMenuVisible, setMenuVisibility]); + // Calculate anchor position when menu becomes visible + useEffect(() => { + if (!actionButtonRef.current || !isMenuVisible) { + return; + } + + calculatePopoverPosition(actionButtonRef as React.RefObject, { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + }).then((position) => { + setPopoverAnchorPosition({...position, vertical: position.vertical - CONST.MODAL.POPOVER_MENU_PADDING}); + }); + }, [isMenuVisible, calculatePopoverPosition, actionButtonRef]); + // 1. Limit the container width to a single column. const outerContainerStyles = [{flexBasis: styles.composerSizeButton.width + styles.composerSizeButton.marginHorizontal * 2}, styles.flexGrow0, styles.flexShrink0]; @@ -517,7 +533,7 @@ function AttachmentPickerWithMenuItems({ }); } }} - anchorPosition={styles.createMenuPositionReportActionCompose(shouldUseNarrowLayout, windowHeight, windowWidth)} + anchorPosition={popoverAnchorPosition ?? {horizontal: 0, vertical: 0}} anchorAlignment={{ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, diff --git a/src/styles/index.ts b/src/styles/index.ts index cff7ad81b677b..e63b68b14cc1c 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5717,13 +5717,6 @@ const dynamicStyles = (theme: ThemeColors) => ...getPopOverVerticalOffset(202 + 40), }) satisfies AnchorPosition, - createMenuPositionReportActionCompose: (shouldUseNarrowLayout: boolean, windowHeight: number, windowWidth: number) => - ({ - // On a narrow layout the menu is displayed in ReportScreen in RHP, so it must be moved from the right side of the screen - horizontal: (shouldUseNarrowLayout ? windowWidth - variables.sideBarWithLHBWidth : variables.sideBarWithLHBWidth + variables.navigationTabBarSize) + 18, - vertical: windowHeight - CONST.MENU_POSITION_REPORT_ACTION_COMPOSE_BOTTOM, - }) satisfies AnchorPosition, - overlayStyles: ({ progress, positionLeftValue, From c9c531478f9a655d9ba2ed555121e3074b571fb1 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Dec 2025 12:16:23 +0100 Subject: [PATCH 3/5] Remove SCREENS.SEARCH.REPORT_RHP const --- src/SCREENS.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 199e494285515..2ac74e298f182 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -47,7 +47,6 @@ const SCREENS = { MONEY_REQUEST_REPORT_VERIFY_ACCOUNT: 'Search_Money_Request_Report_Verify_Account', MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS: 'Search_Money_Request_Report_Hold_Transactions', MONEY_REQUEST_REPORT_REJECT_TRANSACTIONS: 'Search_Money_Request_Report_Reject_Transactions', - REPORT_RHP: 'Search_Report_RHP', COLUMNS_RHP: 'Search_Columns_RHP', REPORT_VERIFY_ACCOUNT: 'Search_Report_Verify_Account', ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP', From 59ebb1dae2c6f53269c93a1122572c1f5d7b7856 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Dec 2025 14:38:53 +0100 Subject: [PATCH 4/5] Move overlays to the wrapper file --- .../SecondaryOverlay.tsx | 68 -------------- .../WideRHPOverlayWrapper/TertiaryOverlay.tsx | 24 ----- .../WideRHPOverlayWrapper/index.tsx | 92 ++++++++++++++++++- .../useModalStackScreenOptions.ts | 3 +- 4 files changed, 90 insertions(+), 97 deletions(-) delete mode 100644 src/components/WideRHPOverlayWrapper/SecondaryOverlay.tsx delete mode 100644 src/components/WideRHPOverlayWrapper/TertiaryOverlay.tsx diff --git a/src/components/WideRHPOverlayWrapper/SecondaryOverlay.tsx b/src/components/WideRHPOverlayWrapper/SecondaryOverlay.tsx deleted file mode 100644 index f971db206e199..0000000000000 --- a/src/components/WideRHPOverlayWrapper/SecondaryOverlay.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import {useRoute} from '@react-navigation/native'; -import React, {useContext} from 'react'; -import { - animatedReceiptPaneRHPWidth, - modalStackOverlaySuperWideRHPPositionLeft, - modalStackOverlayWideRHPPositionLeft, - secondOverlayRHPOnSuperWideRHPProgress, - secondOverlayRHPOnWideRHPProgress, - secondOverlayWideRHPProgress, - WideRHPContext, -} from '@components/WideRHPContextProvider'; -import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; - -export default function SecondaryOverlay() { - const {shouldRenderSecondaryOverlayForRHPOnSuperWideRHP, shouldRenderSecondaryOverlayForRHPOnWideRHP, shouldRenderSecondaryOverlayForWideRHP, superWideRHPRouteKeys, wideRHPRouteKeys} = - useContext(WideRHPContext); - - const route = useRoute(); - - const isWide = !!route?.key && wideRHPRouteKeys.includes(route.key); - const isSuperWide = !!route?.key && superWideRHPRouteKeys.includes(route.key); - - const isRHPDisplayedOnWideRHP = shouldRenderSecondaryOverlayForRHPOnWideRHP && isWide; - const isRHPDisplayedOnSuperWideRHP = shouldRenderSecondaryOverlayForRHPOnSuperWideRHP && isSuperWide; - const isWideRHPDisplayedOnSuperWideRHP = shouldRenderSecondaryOverlayForWideRHP && isSuperWide; - - /** - * 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. - * 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. - * Three cases were described for the secondary overlay: - * 1. Single RHP is displayed on Wide RHP - * 2. Single RHP is displayed on Super Wide RHP - * 3. Wide RHP is displayed on Super Wide RHP route. - * */ - if (isRHPDisplayedOnWideRHP) { - return ( - - ); - } - - if (isWideRHPDisplayedOnSuperWideRHP) { - return ( - - ); - } - - if (isRHPDisplayedOnSuperWideRHP) { - return ( - - ); - } - - return null; -} diff --git a/src/components/WideRHPOverlayWrapper/TertiaryOverlay.tsx b/src/components/WideRHPOverlayWrapper/TertiaryOverlay.tsx deleted file mode 100644 index 7cfae8ff62d16..0000000000000 --- a/src/components/WideRHPOverlayWrapper/TertiaryOverlay.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import {useRoute} from '@react-navigation/native'; -import React, {useContext} from 'react'; -import {modalStackOverlaySuperWideRHPPositionLeft, thirdOverlayProgress, WideRHPContext} from '@components/WideRHPContextProvider'; -import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; - -export default function TertiaryOverlay() { - const {shouldRenderTertiaryOverlay, wideRHPRouteKeys} = useContext(WideRHPContext); - const route = useRoute(); - - const isWide = route?.key && wideRHPRouteKeys.includes(route.key); - - // This overlay is used to cover the space under the narrower RHP screen when more than one RHP width is displayed on the screen - // There is 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. - if (isWide && shouldRenderTertiaryOverlay) { - return ( - - ); - } - - return null; -} diff --git a/src/components/WideRHPOverlayWrapper/index.tsx b/src/components/WideRHPOverlayWrapper/index.tsx index 8996426adb504..e1ffa54fb33b6 100644 --- a/src/components/WideRHPOverlayWrapper/index.tsx +++ b/src/components/WideRHPOverlayWrapper/index.tsx @@ -1,10 +1,94 @@ -import {useFocusEffect} from '@react-navigation/native'; +import {useFocusEffect, useRoute} from '@react-navigation/native'; import React, {useCallback, useContext} from 'react'; -import {WideRHPContext} from '@components/WideRHPContextProvider'; +import { + animatedReceiptPaneRHPWidth, + modalStackOverlaySuperWideRHPPositionLeft, + modalStackOverlayWideRHPPositionLeft, + secondOverlayRHPOnSuperWideRHPProgress, + secondOverlayRHPOnWideRHPProgress, + secondOverlayWideRHPProgress, + thirdOverlayProgress, + WideRHPContext, +} from '@components/WideRHPContextProvider'; +import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; import {navigationRef} from '@libs/Navigation/Navigation'; import NAVIGATORS from '@src/NAVIGATORS'; -import SecondaryOverlay from './SecondaryOverlay'; -import TertiaryOverlay from './TertiaryOverlay'; + +function SecondaryOverlay() { + const {shouldRenderSecondaryOverlayForRHPOnSuperWideRHP, shouldRenderSecondaryOverlayForRHPOnWideRHP, shouldRenderSecondaryOverlayForWideRHP, superWideRHPRouteKeys, wideRHPRouteKeys} = + useContext(WideRHPContext); + + const route = useRoute(); + + const isWide = !!route?.key && wideRHPRouteKeys.includes(route.key); + const isSuperWide = !!route?.key && superWideRHPRouteKeys.includes(route.key); + + const isRHPDisplayedOnWideRHP = shouldRenderSecondaryOverlayForRHPOnWideRHP && isWide; + const isRHPDisplayedOnSuperWideRHP = shouldRenderSecondaryOverlayForRHPOnSuperWideRHP && isSuperWide; + const isWideRHPDisplayedOnSuperWideRHP = shouldRenderSecondaryOverlayForWideRHP && isSuperWide; + + /** + * 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. + * 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. + * Three cases were described for the secondary overlay: + * 1. Single RHP is displayed on Wide RHP + * 2. Single RHP is displayed on Super Wide RHP + * 3. Wide RHP is displayed on Super Wide RHP route. + * */ + if (isRHPDisplayedOnWideRHP) { + return ( + + ); + } + + if (isWideRHPDisplayedOnSuperWideRHP) { + return ( + + ); + } + + if (isRHPDisplayedOnSuperWideRHP) { + return ( + + ); + } + + return null; +} + +function TertiaryOverlay() { + const {shouldRenderTertiaryOverlay, wideRHPRouteKeys} = useContext(WideRHPContext); + const route = useRoute(); + + const isWide = route?.key && wideRHPRouteKeys.includes(route.key); + + // This overlay is used to cover the space under the narrower RHP screen when more than one RHP width is displayed on the screen + // There is 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. + if (isWide && shouldRenderTertiaryOverlay) { + return ( + + ); + } + + return null; +} type WideRHPOverlayWrapperProps = { children: React.ReactNode; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions.ts b/src/libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions.ts index 3050b6850b324..685e72b3fb5a7 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/useModalStackScreenOptions.ts @@ -7,6 +7,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import enhanceCardStyleInterpolator from '@libs/Navigation/AppNavigator/enhanceCardStyleInterpolator'; import hideKeyboardOnSwipe from '@libs/Navigation/AppNavigator/hideKeyboardOnSwipe'; import type {PlatformStackNavigationOptions, PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; + function useWideModalStackScreenOptions() { const styles = useThemeStyles(); @@ -30,7 +31,7 @@ function useWideModalStackScreenOptions() { cardStyleInterpolator = enhanceCardStyleInterpolator(CardStyleInterpolators.forHorizontalIOS, { cardStyle: styles.wideRHPExtendedCardInterpolatorStyles, }); - // single RHPs displayed above the wide RHP need to be positioned + // single RHPs displayed above the wide RHP need to be positioned } else if (superWideRHPRouteKeys.length > 0 || wideRHPRouteKeys.length > 0) { cardStyleInterpolator = enhanceCardStyleInterpolator(CardStyleInterpolators.forHorizontalIOS, { cardStyle: styles.singleRHPExtendedCardInterpolatorStyles, From cb019f7055de3a2407b239610c76367ac2e02bea Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 19 Dec 2025 10:28:18 +0100 Subject: [PATCH 5/5] Fix animations when navigating between search reports --- .../Navigation/AppNavigator/Navigators/RightModalNavigator.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 253b740123388..0813bc65fc45f 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -27,6 +27,7 @@ import calculateSuperWideRHPWidth from '@libs/Navigation/helpers/calculateSuperW import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; +import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {AuthScreensParamList, RightModalNavigatorParamList} from '@navigation/types'; import variables from '@styles/variables'; @@ -337,7 +338,7 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) {