From 51648b04c27aa18c62c6fc67efb386e15087fa9a Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 4 Aug 2025 13:41:54 +0200 Subject: [PATCH 01/56] adjust more button in money request header --- src/components/MoneyRequestHeader.tsx | 17 ++++++++++++++++- src/styles/index.ts | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 218c8fee2901..a84a1f5b4efb 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -52,6 +52,7 @@ import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusB import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import MoneyRequestReportTransactionsNavigation from './MoneyRequestReportView/MoneyRequestReportTransactionsNavigation'; import {useSearchContext} from './Search/SearchContext'; +import {WideRHPContext} from './WideRHPContextProvider'; type MoneyRequestHeaderProps = { /** The report currently being looked at */ @@ -106,6 +107,8 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre // If the parent report is a selfDM, it should always be opened in the Inbox tab const shouldOpenParentReportInCurrentTab = !isSelfDM(parentReport); + const {wideRHPRouteKeys} = useContext(WideRHPContext); + const markAsCash = useCallback(() => { markAsCashAction(transaction?.transactionID, reportID); }, [reportID, transaction?.transactionID]); @@ -277,6 +280,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }; const applicableSecondaryActions = secondaryActions.map((action) => secondaryActionsImplementation[action]); + const shouldDisplayWideRHPVersion = wideRHPRouteKeys.length > 0 && isSmallScreenWidth; return ( @@ -315,12 +319,23 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre )} )} + {!!applicableSecondaryActions.length && !shouldDisplayWideRHPVersion && ( + {}} + shouldAlwaysShowDropdownMenu + customText={translate('common.more')} + options={applicableSecondaryActions} + isSplitButton={false} + wrapperStyle={[!primaryAction && styles.flexGrow4, {marginRight: 12}]} + /> + )} {shouldDisplayTransactionNavigation && } {shouldUseNarrowLayout && ( {!!primaryAction && {primaryActionImplementation[primaryAction]}} - {!!applicableSecondaryActions.length && ( + {!!applicableSecondaryActions.length && shouldDisplayWideRHPVersion && ( {}} diff --git a/src/styles/index.ts b/src/styles/index.ts index c7f64281317c..ac8814f16036 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5766,6 +5766,10 @@ const styles = (theme: ThemeColors) => width: Animated.add(variables.sideBarWidth, receiptPaneRHPWidth), }, + smallMoreButton: { + marginRight: 12, + }, + getTestToolsNavigatorOuterView: (shouldUseNarrowLayout: boolean) => ({ flex: 1, justifyContent: shouldUseNarrowLayout ? 'flex-end' : 'center', From 8521797377ca611fb11bfa7059121a622ed4e19d Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 4 Aug 2025 16:06:53 +0200 Subject: [PATCH 02/56] hide receipt image on the right panel on wide rhp --- .../ReportActionItem/MoneyRequestView.tsx | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 64a4867ef2d3..42c3c4ebc8d4 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1,5 +1,5 @@ import {Str} from 'expensify-common'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Icon from '@components/Icon'; @@ -11,12 +11,14 @@ import {usePolicyCategories, usePolicyTags} from '@components/OnyxListItemProvid import Switch from '@components/Switch'; import Text from '@components/Text'; import ViolationMessages from '@components/ViolationMessages'; +import {WideRHPContext} from '@components/WideRHPContextProvider'; import useActiveRoute from '@hooks/useActiveRoute'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePrevious from '@hooks/usePrevious'; import useReportIsArchived from '@hooks/useReportIsArchived'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -546,18 +548,25 @@ function MoneyRequestView({ const actualParentReport = isFromMergeTransaction ? getReportOrDraftReport(getReportIDForExpense(updatedTransaction)) : parentReport; const shouldShowReport = !!parentReportID || !!actualParentReport; + // In this case we want to use this value. The shouldUseNarrowLayout will always be true as this case is handled when we display ReportScreen in RHP. + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth} = useResponsiveLayout(); + const {wideRHPRouteKeys} = useContext(WideRHPContext); + return ( {shouldShowAnimatedBackground && } <> - + {(wideRHPRouteKeys.length === 0 || isSmallScreenWidth) && ( + + )} {isCustomUnitOutOfPolicy && isPerDiemRequest && ( Date: Mon, 4 Aug 2025 16:18:52 +0200 Subject: [PATCH 03/56] add report prop --- .../ReportActionItem/MoneyRequestReceiptView.tsx | 1 + src/components/ReportActionItem/ReportActionItemImage.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index c7d0425db61d..33959ac55c06 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -272,6 +272,7 @@ function MoneyRequestReceiptView({allReports, report, readonly = false, updatedT readonly={readonly || !canEditReceipt} isFromReviewDuplicates={isFromReviewDuplicates} mergeTransactionID={mergeTransactionID} + report={report} /> )} diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index ba8d6d8086c8..5bd2b91d85ad 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -19,7 +19,7 @@ import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import type {Transaction} from '@src/types/onyx'; +import type {Report, Transaction} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type ReportActionItemImageProps = { @@ -69,6 +69,9 @@ type ReportActionItemImageProps = { /** Whether the receipt empty state should extend to the full height of the container. */ shouldUseFullHeight?: boolean; + + /** The report associated with this image, if any. Used for additional context or logic. */ + report?: OnyxEntry; }; /** @@ -94,6 +97,7 @@ function ReportActionItemImage({ mergeTransactionID, onPress, shouldUseFullHeight, + report: reportProp, }: ReportActionItemImageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -159,7 +163,7 @@ function ReportActionItemImage({ onPress={() => Navigation.navigate( ROUTES.TRANSACTION_RECEIPT.getRoute( - transactionThreadReport?.reportID ?? report?.reportID, + transactionThreadReport?.reportID ?? report?.reportID ?? reportProp?.reportID, transaction?.transactionID, readonly, isFromReviewDuplicates, From 3c5965a11ed604307b70843f8d1b9bf616b58129 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 4 Aug 2025 16:44:51 +0200 Subject: [PATCH 04/56] add receipt view to report screen --- src/pages/home/ReportScreen.tsx | 100 +++++++++++++++++++------------- 1 file changed, 60 insertions(+), 40 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 57bf14a4de3f..cd1e1372ad4c 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -13,8 +13,10 @@ import MoneyReportHeader from '@components/MoneyReportHeader'; import MoneyRequestHeader from '@components/MoneyRequestHeader'; import MoneyRequestReportActionsList from '@components/MoneyRequestReportView/MoneyRequestReportActionsList'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import MoneyRequestReceiptView from '@components/ReportActionItem/MoneyRequestReceiptView'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; +import {useShowWideRHPVersion} from '@components/WideRHPContextProvider'; import useAppFocusEvent from '@hooks/useAppFocusEvent'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useDeepCompareRef from '@hooks/useDeepCompareRef'; @@ -144,6 +146,7 @@ function getParentReportAction(parentReportActions: OnyxEntry )} - - {(!report || shouldWaitForTransactions) && } - {!!report && !shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( - - ) : null} - {!!report && shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( - - ) : null} - {isCurrentReportLoadedFromOnyx ? ( - - ) : null} + + {shouldShowWideRHP && ( + + + + )} + + {(!report || shouldWaitForTransactions) && } + {!!report && !shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( + + ) : null} + {!!report && shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( + + ) : null} + {isCurrentReportLoadedFromOnyx ? ( + + ) : null} + From fc36c600ae8e0057036fbd6f76e8a20d8394faa5 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 6 Aug 2025 14:51:35 +0200 Subject: [PATCH 05/56] make size of receipt images flexible --- .../MoneyRequestReceiptView.tsx | 35 ++++++++++++++++--- .../ReportActionItemImage.tsx | 10 ++++-- src/pages/home/ReportScreen.tsx | 13 ++++--- src/styles/index.ts | 20 +++++++++++ 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 33959ac55c06..23da0c6bd676 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -1,6 +1,7 @@ import mapValues from 'lodash/mapValues'; import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -64,6 +65,9 @@ type MoneyRequestReceiptViewProps = { /** Merge transaction ID to show in merge transaction flow */ mergeTransactionID?: string; + + /** Whether the receipt view should fill the given space */ + fillSpace?: boolean; }; const receiptImageViolationNames: OnyxTypes.ViolationName[] = [ @@ -77,7 +81,15 @@ const receiptImageViolationNames: OnyxTypes.ViolationName[] = [ const receiptFieldViolationNames: OnyxTypes.ViolationName[] = [CONST.VIOLATIONS.MODIFIED_AMOUNT, CONST.VIOLATIONS.MODIFIED_DATE]; -function MoneyRequestReceiptView({allReports, report, readonly = false, updatedTransaction, isFromReviewDuplicates = false, mergeTransactionID}: MoneyRequestReceiptViewProps) { +function MoneyRequestReceiptView({ + allReports, + report, + readonly = false, + updatedTransaction, + isFromReviewDuplicates = false, + fillSpace = false, + mergeTransactionID, +}: MoneyRequestReceiptViewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -202,10 +214,20 @@ function MoneyRequestReceiptView({allReports, report, readonly = false, updatedT clearAllRelatedReportActionErrors(report.reportID, parentReportAction); }, [transaction, chatReport, parentReportAction, linkedTransactionID, report?.reportID]); - const receiptStyle = shouldUseNarrowLayout ? styles.expenseViewImageSmall : styles.expenseViewImage; + let receiptStyle: StyleProp; + + if (fillSpace && shouldShowReceiptEmptyState) { + receiptStyle = styles.fullHeight; + } else if (fillSpace) { + receiptStyle = styles.heightAuto; + } else { + receiptStyle = shouldUseNarrowLayout ? styles.expenseViewImageSmall : styles.expenseViewImage; + } + // For empty receipt should be fullHeight + // For the rest, expand to match the content return ( - + {shouldShowReceiptAudit && ( {hasReceipt && ( ; }; @@ -135,7 +135,9 @@ function ReportActionItemImage({ fallbackIcon: Expensicons.Receipt, fallbackIconSize: isSingleImage ? variables.iconSizeSuperLarge : variables.iconSizeExtraLarge, isAuthTokenRequired: true, - shouldUseInitialObjectPosition: isMapDistanceRequest, + + // If the image is full height, use initial position to make sure it will grow properly to fill the container + shouldUseInitialObjectPosition: isMapDistanceRequest && !shouldUseFullHeight, }; } else if (isLocalFile && filename && Str.isPDF(filename) && typeof attachmentModalSource === 'string') { propsObj = {isPDFThumbnail: true, source: attachmentModalSource}; @@ -146,7 +148,9 @@ function ReportActionItemImage({ shouldUseThumbnailImage: true, isAuthTokenRequired: false, source: thumbnail ?? image ?? '', - shouldUseInitialObjectPosition: isMapDistanceRequest, + + // If the image is full height, use initial position to make sure it will grow properly to fill the container + shouldUseInitialObjectPosition: isMapDistanceRequest && !shouldUseFullHeight, isEmptyReceipt, onPress, }; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index cd1e1372ad4c..3d650e864767 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -16,6 +16,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import MoneyRequestReceiptView from '@components/ReportActionItem/MoneyRequestReceiptView'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import {useShowWideRHPVersion} from '@components/WideRHPContextProvider'; import useAppFocusEvent from '@hooks/useAppFocusEvent'; import useCurrentReportID from '@hooks/useCurrentReportID'; @@ -851,11 +852,13 @@ function ReportScreen({route, navigation}: ReportScreenProps) { {shouldShowWideRHP && ( - + + + )} width: Animated.add(variables.sideBarWidth, receiptPaneRHPWidth), }, + fullHeight: { + height: '100%', + }, + + heightAuto: { + height: 'auto', + }, + + wideRHPMoneyRequestReceiptViewContainer: { + backgroundColor: theme.appBG, + width: variables.sideBarWidth, + height: '100%', + borderRightWidth: 1, + borderColor: theme.border, + }, + wideRHPMoneyRequestReceiptViewScrollView: { + paddingTop: 12, + minHeight: '100%', + }, + smallMoreButton: { marginRight: 12, }, From 285d09f56e9cb6894236c6226ea33ba4ba03196c Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 6 Aug 2025 17:23:19 +0200 Subject: [PATCH 06/56] fix size of loading receipt image --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- src/styles/index.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 23da0c6bd676..c8bbc4f55743 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -219,7 +219,7 @@ function MoneyRequestReceiptView({ if (fillSpace && shouldShowReceiptEmptyState) { receiptStyle = styles.fullHeight; } else if (fillSpace) { - receiptStyle = styles.heightAuto; + receiptStyle = styles.flexibleHeight; } else { receiptStyle = shouldUseNarrowLayout ? styles.expenseViewImageSmall : styles.expenseViewImage; } diff --git a/src/styles/index.ts b/src/styles/index.ts index b96c7ff7096f..3f6ac4b7c314 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5770,8 +5770,9 @@ const styles = (theme: ThemeColors) => height: '100%', }, - heightAuto: { + flexibleHeight: { height: 'auto', + minHeight: 200, }, wideRHPMoneyRequestReceiptViewContainer: { From 716c1d1bbea032dd7b383d4c8328cec846eab3b3 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Fri, 8 Aug 2025 17:58:21 +0200 Subject: [PATCH 07/56] fix condition for showing wide RHP --- src/pages/home/ReportScreen.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 3d650e864767..f51505e2489d 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -51,6 +51,7 @@ import { isCreatedAction, isDeletedParentAction, isMoneyRequestAction, + isTransactionThread, isWhisperAction, shouldReportActionBeVisible, } from '@libs/ReportActionsUtils'; @@ -794,7 +795,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const {isSmallScreenWidth} = useResponsiveLayout(); // @ts-expect-error TODO somebody messed up the types - const shouldShowWideRHP = route.name === SCREENS.SEARCH.REPORT_RHP && !isSmallScreenWidth; + const shouldShowWideRHP = route.name === SCREENS.SEARCH.REPORT_RHP && !isSmallScreenWidth && isTransactionThread(parentReportAction); useShowWideRHPVersion(shouldShowWideRHP); // Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger. // If we have cached reportActions, they will be shown immediately. From dd86365fdd9967cdb9438431c806e2ce6965549d Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 14 Aug 2025 13:34:55 +0200 Subject: [PATCH 08/56] fix thumbnails sizing --- src/components/ReceiptImage.tsx | 1 + .../MoneyRequestReceiptView.tsx | 1 + .../ReportActionItemImage.tsx | 21 ++++++++++++------- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx index 27af8202044e..2ac3af433829 100644 --- a/src/components/ReceiptImage.tsx +++ b/src/components/ReceiptImage.tsx @@ -204,6 +204,7 @@ function ReceiptImage({ loadingIconSize={loadingIconSize} loadingIndicatorStyles={loadingIndicatorStyles} shouldShowOfflineIndicator={false} + objectPosition={shouldUseInitialObjectPosition ? CONST.IMAGE_OBJECT_POSITION.INITIAL : CONST.IMAGE_OBJECT_POSITION.TOP} /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index c8bbc4f55743..4811ccc8df14 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -285,6 +285,7 @@ function MoneyRequestReceiptView({ {hasReceipt && ( ; + + /** Whether to use the thumbnail image instead of the full image */ + shouldUseThumbnailImage?: boolean; }; /** @@ -98,6 +101,7 @@ function ReportActionItemImage({ onPress, shouldUseFullHeight, report: reportProp, + shouldUseThumbnailImage, }: ReportActionItemImageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -120,7 +124,7 @@ function ReportActionItemImage({ ); } - const attachmentModalSource = tryResolveUrlFromApiRoot(image ?? ''); + const originalImageSource = tryResolveUrlFromApiRoot(image ?? ''); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail ?? ''); const isEReceipt = transaction && !hasReceiptSource(transaction) && hasEReceipt(transaction); @@ -130,8 +134,11 @@ function ReportActionItemImage({ propsObj = {isEReceipt: true, transactionID: transaction.transactionID, iconSize: isSingleImage ? 'medium' : ('small' as IconSize)}; } else if (thumbnail && !isLocalFile) { propsObj = { - shouldUseThumbnailImage: true, - source: thumbnailSource, + shouldUseThumbnailImage: shouldUseThumbnailImage ?? true, + + // PDF won't have originalImage that we can use. Use thumbnail instead + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + source: shouldUseThumbnailImage || (filename && Str.isPDF(filename)) ? thumbnailSource : originalImageSource, fallbackIcon: Expensicons.Receipt, fallbackIconSize: isSingleImage ? variables.iconSizeSuperLarge : variables.iconSizeExtraLarge, isAuthTokenRequired: true, @@ -139,15 +146,15 @@ function ReportActionItemImage({ // If the image is full height, use initial position to make sure it will grow properly to fill the container shouldUseInitialObjectPosition: isMapDistanceRequest && !shouldUseFullHeight, }; - } else if (isLocalFile && filename && Str.isPDF(filename) && typeof attachmentModalSource === 'string') { - propsObj = {isPDFThumbnail: true, source: attachmentModalSource}; + } else if (isLocalFile && filename && Str.isPDF(filename) && typeof originalImageSource === 'string') { + propsObj = {isPDFThumbnail: true, source: originalImageSource}; } else { propsObj = { isThumbnail, ...(isThumbnail && {iconSize: (isSingleImage ? 'medium' : 'small') as IconSize, fileExtension}), - shouldUseThumbnailImage: true, + shouldUseThumbnailImage: shouldUseThumbnailImage ?? true, isAuthTokenRequired: false, - source: thumbnail ?? image ?? '', + source: shouldUseThumbnailImage ? (thumbnail ?? image ?? '') : originalImageSource, // If the image is full height, use initial position to make sure it will grow properly to fill the container shouldUseInitialObjectPosition: isMapDistanceRequest && !shouldUseFullHeight, From 712c68715c95c24a53020eaf0ac2286b96fca18b Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 14 Aug 2025 14:55:06 +0200 Subject: [PATCH 09/56] fix empty div in MoneyRequestHeader --- src/components/MoneyRequestHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index a84a1f5b4efb..10eec8cabc59 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -332,7 +332,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre )} {shouldDisplayTransactionNavigation && } - {shouldUseNarrowLayout && ( + {shouldUseNarrowLayout && (!!primaryAction || (!!applicableSecondaryActions.length && shouldDisplayWideRHPVersion)) && ( {!!primaryAction && {primaryActionImplementation[primaryAction]}} {!!applicableSecondaryActions.length && shouldDisplayWideRHPVersion && ( From fc2daa8a64fdde7d34cf3ed6395d33bceca00f21 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 14 Aug 2025 14:57:41 +0200 Subject: [PATCH 10/56] fix spacing around empty receipt image --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 4811ccc8df14..ef531699fe1f 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -239,7 +239,7 @@ function MoneyRequestReceiptView({ {shouldShowReceiptEmptyState && ( Date: Thu, 14 Aug 2025 15:23:48 +0200 Subject: [PATCH 11/56] fix spacing around empty receipt image v2 --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index ef531699fe1f..da38a08c8104 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -279,7 +279,7 @@ function MoneyRequestReceiptView({ } }} dismissError={dismissReceiptError} - style={[shouldShowReceiptEmptyState ? styles.mv3 : styles.mv3, styles.flex1]} + style={[styles.mb5, styles.flex1, styles.mt3]} contentContainerStyle={styles.flex1} > {hasReceipt && ( From f93c7a41d8df455ea28f41aee25caaafda94f157 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 18 Aug 2025 15:41:29 +0200 Subject: [PATCH 12/56] make wide RHP responsive in range 800px to 840px --- src/pages/home/ReportScreen.tsx | 7 ++++--- src/styles/index.ts | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index f51505e2489d..b9f88e1cee10 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -3,7 +3,8 @@ import {useIsFocused} from '@react-navigation/native'; import {deepEqual} from 'fast-equals'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {FlatList, ViewStyle} from 'react-native'; -import {DeviceEventEmitter, InteractionManager, View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import {Animated, DeviceEventEmitter, InteractionManager, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Banner from '@components/Banner'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -852,7 +853,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { {shouldShowWideRHP && ( - + - + )} wideRHPMoneyRequestReceiptViewContainer: { backgroundColor: theme.appBG, - width: variables.sideBarWidth, + width: receiptPaneRHPWidth, height: '100%', borderRightWidth: 1, borderColor: theme.border, }, + wideRHPMoneyRequestReceiptViewScrollView: { paddingTop: 12, minHeight: '100%', From 84629f8230013c8abfd0e3f782a87bdc6d3861eb Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 18 Aug 2025 16:25:09 +0200 Subject: [PATCH 13/56] add optimistic marking reportID as expense --- .../MoneyRequestReportTransactionList.tsx | 7 +++++-- src/components/Search/index.tsx | 8 ++++++-- .../SelectionList/Search/TransactionGroupListItem.tsx | 5 ++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index b4b5b03d3b56..b2929c338d00 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import isEmpty from 'lodash/isEmpty'; -import React, {memo, useCallback, useMemo, useState} from 'react'; +import React, {memo, useCallback, useContext, useMemo, useState} from 'react'; import {View} from 'react-native'; import type {TupleToUnion} from 'type-fest'; import Checkbox from '@components/Checkbox'; @@ -11,6 +11,7 @@ import {useSession} from '@components/OnyxListItemProvider'; import {useSearchContext} from '@components/Search/SearchContext'; import type {SearchColumnType, SortOrder} from '@components/Search/types'; import Text from '@components/Text'; +import {WideRHPContext} from '@components/WideRHPContextProvider'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -128,6 +129,7 @@ function MoneyRequestReportTransactionList({ const {translate, localeCompare} = useLocalize(); // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth, isMediumScreenWidth} = useResponsiveLayout(); + const {markReportIDAsExpense} = useContext(WideRHPContext); const [isModalVisible, setIsModalVisible] = useState(false); const [selectedTransactionID, setSelectedTransactionID] = useState(''); @@ -217,10 +219,11 @@ function MoneyRequestReportTransactionList({ // to display prev/next arrows in RHP for navigation const sortedSiblingTransactionReportIDs = getThreadReportIDsForTransactions(reportActions, sortedTransactions); setActiveTransactionThreadIDs(sortedSiblingTransactionReportIDs).then(() => { + markReportIDAsExpense(reportIDToNavigate); Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(routeParams)); }); }, - [report, reportActions, sortedTransactions], + [report, reportActions, sortedTransactions, markReportIDAsExpense], ); const {amountColumnSize, dateColumnSize, taxAmountColumnSize} = useMemo(() => { diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index a4a19fe65454..f65e3b06338e 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -1,6 +1,6 @@ import {findFocusedRoute, useFocusEffect, useIsFocused, useNavigation} from '@react-navigation/native'; import type {ContentStyle} from '@shopify/flash-list'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; import {View} from 'react-native'; import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; @@ -9,6 +9,7 @@ import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOffli import SearchTableHeader, {getExpenseHeaders} from '@components/SelectionList/SearchTableHeader'; import type {ReportActionListItemType, SearchListItem, SelectionListHandle, TransactionGroupListItemType, TransactionListItemType} from '@components/SelectionList/types'; import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; +import {WideRHPContext} from '@components/WideRHPContextProvider'; import useCardFeedsForDisplay from '@hooks/useCardFeedsForDisplay'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -196,6 +197,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS const {isSmallScreenWidth, isLargeScreenWidth} = useResponsiveLayout(); const navigation = useNavigation>(); const isFocused = useIsFocused(); + const {markReportIDAsExpense} = useContext(WideRHPContext); const { setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, @@ -670,9 +672,11 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS return; } + markReportIDAsExpense(reportID); + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo})); }, - [isMobileSelectionModeEnabled, toggleTransaction, queryJSON, handleSearch, searchKey, reportActionsArray, hash], + [isMobileSelectionModeEnabled, toggleTransaction, queryJSON, handleSearch, searchKey, reportActionsArray, hash, markReportIDAsExpense], ); const currentColumns = useMemo(() => { diff --git a/src/components/SelectionList/Search/TransactionGroupListItem.tsx b/src/components/SelectionList/Search/TransactionGroupListItem.tsx index 7356457917b7..67a6bd794ed1 100644 --- a/src/components/SelectionList/Search/TransactionGroupListItem.tsx +++ b/src/components/SelectionList/Search/TransactionGroupListItem.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import AnimatedCollapsible from '@components/AnimatedCollapsible'; import Button from '@components/Button'; @@ -20,6 +20,7 @@ import type { } from '@components/SelectionList/types'; import Text from '@components/Text'; import TransactionItemRow from '@components/TransactionItemRow'; +import {WideRHPContext} from '@components/WideRHPContextProvider'; import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -125,6 +126,7 @@ function TransactionGroupListItem({ const [isExpanded, setIsExpanded] = useState(false); const isEmpty = transactions.length === 0; + const {markReportIDAsExpense} = useContext(WideRHPContext); // Currently only the transaction report groups have transactions where the empty view makes sense const shouldDisplayEmptyView = isEmpty && isGroupByReports; const isDisabledOrEmpty = isEmpty || isDisabled; @@ -169,6 +171,7 @@ function TransactionGroupListItem({ createAndOpenSearchTransactionThread(transactionItem, iouAction, currentSearchHash, backTo); return; } + markReportIDAsExpense(reportID); Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo})); }); }; From 25c1b48a6a34aa3111412cdc523ee1c44a5dd1a9 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 19 Aug 2025 14:43:40 +0200 Subject: [PATCH 14/56] add optimistic marking reportID as expense v2 --- .../MoneyRequestReportTransactionsNavigation.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx index 1dd4ad4ede4c..122b529d38ed 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx @@ -1,6 +1,7 @@ import {findFocusedRoute} from '@react-navigation/native'; -import React, {useEffect, useMemo} from 'react'; +import React, {useContext, useEffect, useMemo} from 'react'; import PrevNextButtons from '@components/PrevNextButtons'; +import {WideRHPContext} from '@components/WideRHPContextProvider'; import useOnyx from '@hooks/useOnyx'; import {clearActiveTransactionThreadIDs} from '@libs/actions/TransactionThreadNavigation'; import Navigation from '@navigation/Navigation'; @@ -19,6 +20,8 @@ function MoneyRequestReportTransactionsNavigation({currentReportID}: MoneyReques canBeMissing: true, }); + const {markReportIDAsExpense} = useContext(WideRHPContext); + const {prevReportID, nextReportID} = useMemo(() => { if (!reportIDsList || reportIDsList.length < 2) { return {prevReportID: undefined, nextReportID: undefined}; @@ -57,11 +60,15 @@ function MoneyRequestReportTransactionsNavigation({currentReportID}: MoneyReques onNext={(e) => { const backTo = Navigation.getActiveRoute(); e?.preventDefault(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + markReportIDAsExpense(nextReportID!); Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: nextReportID, backTo}), {forceReplace: true}); }} onPrevious={(e) => { const backTo = Navigation.getActiveRoute(); e?.preventDefault(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + markReportIDAsExpense(prevReportID!); Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: prevReportID, backTo}), {forceReplace: true}); }} /> From a534abfaa55d93aa0618288fc6c58f0c768becb6 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 20 Aug 2025 17:22:16 +0200 Subject: [PATCH 15/56] fix more button --- src/components/MoneyReportHeader.tsx | 4 ++++ src/components/MoneyRequestHeader.tsx | 19 ++++--------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 8df62a88e343..5fe9e4f1ed95 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -129,6 +129,7 @@ import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import {useSearchContext} from './Search/SearchContext'; import AnimatedSettlementButton from './SettlementButton/AnimatedSettlementButton'; import Text from './Text'; +import {WideRHPContext} from './WideRHPContextProvider'; type MoneyReportHeaderProps = { /** The report currently being looked at */ @@ -309,6 +310,9 @@ function MoneyReportHeader({ const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey} = useSearchContext(); + const {wideRHPRouteKeys} = useContext(WideRHPContext); + const shouldDisplayWideRHPVersion = wideRHPRouteKeys.length > 0 && !isSmallScreenWidth; + const beginExportWithTemplate = useCallback( (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => { if (!moneyRequestReport) { diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 10eec8cabc59..37c4525bd0eb 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -280,7 +280,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }; const applicableSecondaryActions = secondaryActions.map((action) => secondaryActionsImplementation[action]); - const shouldDisplayWideRHPVersion = wideRHPRouteKeys.length > 0 && isSmallScreenWidth; + const shouldDisplayWideRHPVersion = wideRHPRouteKeys.length > 0 && !isSmallScreenWidth; return ( @@ -304,7 +304,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre shouldEnableDetailPageNavigation openParentReportInCurrentTab={shouldOpenParentReportInCurrentTab} > - {!shouldUseNarrowLayout && ( + {(!shouldUseNarrowLayout || shouldDisplayWideRHPVersion) && ( {!!primaryAction && primaryActionImplementation[primaryAction]} {!!applicableSecondaryActions.length && ( @@ -319,23 +319,12 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre )} )} - {!!applicableSecondaryActions.length && !shouldDisplayWideRHPVersion && ( - {}} - shouldAlwaysShowDropdownMenu - customText={translate('common.more')} - options={applicableSecondaryActions} - isSplitButton={false} - wrapperStyle={[!primaryAction && styles.flexGrow4, {marginRight: 12}]} - /> - )} {shouldDisplayTransactionNavigation && } - {shouldUseNarrowLayout && (!!primaryAction || (!!applicableSecondaryActions.length && shouldDisplayWideRHPVersion)) && ( + {shouldUseNarrowLayout && !shouldDisplayWideRHPVersion && ( {!!primaryAction && {primaryActionImplementation[primaryAction]}} - {!!applicableSecondaryActions.length && shouldDisplayWideRHPVersion && ( + {!!applicableSecondaryActions.length && ( {}} From c553792c8dfd7cb7b78b8bb8fe14fd3988875310 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 20 Aug 2025 17:26:30 +0200 Subject: [PATCH 16/56] extend condition for showing wide rhp --- src/pages/home/ReportScreen.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index b9f88e1cee10..7935ad3f4323 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -795,9 +795,14 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); - // @ts-expect-error TODO somebody messed up the types - const shouldShowWideRHP = route.name === SCREENS.SEARCH.REPORT_RHP && !isSmallScreenWidth && isTransactionThread(parentReportAction); + const shouldShowWideRHP = + // @ts-expect-error @TODO-RHP somebody messed up the types + route.name === SCREENS.SEARCH.REPORT_RHP && + !isSmallScreenWidth && + (isTransactionThread(parentReportAction) || parentReportAction?.childType === CONST.REPORT.TYPE.EXPENSE || parentReportAction?.childType === CONST.REPORT.TYPE.IOU); + useShowWideRHPVersion(shouldShowWideRHP); + // Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger. // If we have cached reportActions, they will be shown immediately. // We aim to display a loader first, then fetch relevant reportActions, and finally show them. From 2d27e5b597ffcdf375ac08ad337d1ea2a5a60afd Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 20 Aug 2025 19:03:17 +0200 Subject: [PATCH 17/56] fix wrong report for receipt view --- src/pages/home/ReportScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7935ad3f4323..e23f60ad785e 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -862,7 +862,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { From 959ecf4f0ab2867af6357696040c98bb03da1e9a Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Fri, 22 Aug 2025 14:24:26 +0200 Subject: [PATCH 18/56] fix more button v2 --- src/components/MoneyReportHeader.tsx | 4 ++-- src/components/MoneyRequestHeader.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 5fe9e4f1ed95..46103a0fc14c 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1236,7 +1236,7 @@ function MoneyReportHeader({ shouldShowBorderBottom={false} shouldEnableDetailPageNavigation > - {!shouldDisplayNarrowVersion && ( + {(!shouldDisplayNarrowVersion || shouldDisplayWideRHPVersion) && ( {!!primaryAction && !shouldShowSelectedTransactionsButton && primaryActionsImplementation[primaryAction]} {!!applicableSecondaryActions.length && !shouldShowSelectedTransactionsButton && KYCMoreDropdown} @@ -1254,7 +1254,7 @@ function MoneyReportHeader({ )} - {shouldDisplayNarrowVersion && !shouldShowSelectedTransactionsButton && ( + {shouldDisplayNarrowVersion && !shouldShowSelectedTransactionsButton && !shouldDisplayWideRHPVersion && ( {!!primaryAction && {primaryActionsImplementation[primaryAction]}} {!!applicableSecondaryActions.length && KYCMoreDropdown} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 37c4525bd0eb..15973284e32c 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -305,7 +305,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre openParentReportInCurrentTab={shouldOpenParentReportInCurrentTab} > {(!shouldUseNarrowLayout || shouldDisplayWideRHPVersion) && ( - + {!!primaryAction && primaryActionImplementation[primaryAction]} {!!applicableSecondaryActions.length && ( Date: Mon, 25 Aug 2025 15:18:07 +0200 Subject: [PATCH 19/56] Fix dismissing modal when creating transaction from wide rhp --- src/libs/actions/IOU.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 966c3df86146..ca0d2f34e7b0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -217,8 +217,8 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import type {IOUAction, IOUActionParams, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; +import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Accountant, Attendee, Participant, Split} from '@src/types/onyx/IOU'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; @@ -884,6 +884,14 @@ function dismissModalAndOpenReportInInboxTab(reportID?: string) { if (isReportOpenInRHP(rootState)) { const rhpKey = rootState.routes.at(-1)?.state?.key; if (rhpKey) { + const hasMultipleTransactions = Object.values(allTransactions).filter((transaction) => transaction?.reportID === reportID).length > 0; + if (hasMultipleTransactions && reportID) { + Navigation.dismissModal(); + InteractionManager.runAfterInteractions(() => { + Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID})); + }); + return; + } Navigation.pop(rhpKey); return; } From 461b9d059c59aa8856a76222b14af18521993117 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 25 Aug 2025 17:00:45 +0200 Subject: [PATCH 20/56] fix loader --- src/components/ImageWithSizeCalculation.tsx | 21 ++++++++++++++++++- src/components/ReceiptImage.tsx | 2 ++ src/components/ThumbnailImage.tsx | 11 ++++++++++ .../DataCells/ReceiptCell.tsx | 2 +- src/styles/index.ts | 4 ++++ 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index 66895439212b..939df5f504d0 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -3,6 +3,7 @@ import type {ImageSourcePropType, StyleProp, ViewStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; import CONST from '@src/CONST'; +import type {FullScreenLoadingIndicatorIconSize} from './FullscreenLoadingIndicator'; import RESIZE_MODES from './Image/resizeModes'; import type {ImageObjectPosition} from './Image/types'; import ImageWithLoading from './ImageWithLoading'; @@ -36,6 +37,12 @@ type ImageWithSizeCalculationProps = { /** The object position of image */ objectPosition?: ImageObjectPosition; + + /** The size of the loading indicator */ + loadingIconSize?: FullScreenLoadingIndicatorIconSize; + + /** The style of the loading indicator */ + loadingIndicatorStyles?: StyleProp; }; /** @@ -44,7 +51,17 @@ type ImageWithSizeCalculationProps = { * performing some calculation on a network image after fetching dimensions so * it can be appropriately resized. */ -function ImageWithSizeCalculation({url, altText, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL}: ImageWithSizeCalculationProps) { +function ImageWithSizeCalculation({ + url, + altText, + style, + onMeasure, + onLoadFailure, + isAuthTokenRequired, + objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, + loadingIconSize, + loadingIndicatorStyles, +}: ImageWithSizeCalculationProps) { const styles = useThemeStyles(); const source = useMemo(() => (typeof url === 'string' ? {uri: url} : url), [url]); @@ -70,6 +87,8 @@ function ImageWithSizeCalculation({url, altText, style, onMeasure, onLoadFailure }); }} objectPosition={objectPosition} + loadingIconSize={loadingIconSize} + loadingIndicatorStyles={loadingIndicatorStyles} /> ); } diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx index 2ac3af433829..10702b10dbe0 100644 --- a/src/components/ReceiptImage.tsx +++ b/src/components/ReceiptImage.tsx @@ -192,6 +192,8 @@ function ReceiptImage({ fallbackIconColor={fallbackIconColor} fallbackIconBackground={fallbackIconBackground} objectPosition={shouldUseInitialObjectPosition ? CONST.IMAGE_OBJECT_POSITION.INITIAL : CONST.IMAGE_OBJECT_POSITION.TOP} + loadingIconSize={loadingIconSize} + loadingIndicatorStyles={loadingIndicatorStyles} /> ); } diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index a155d709955f..04076ba7a1fe 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -10,6 +10,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; import AttachmentDeletedIndicator from './AttachmentDeletedIndicator'; +import type {FullScreenLoadingIndicatorIconSize} from './FullscreenLoadingIndicator'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import type {ImageObjectPosition} from './Image/types'; @@ -65,6 +66,12 @@ type ThumbnailImageProps = { /** Callback fired when the image has been measured */ onMeasure?: () => void; + + /** The size of the loading indicator */ + loadingIconSize?: FullScreenLoadingIndicatorIconSize; + + /** The style of the loading indicator */ + loadingIndicatorStyles?: StyleProp; }; type UpdateImageSizeParams = { @@ -88,6 +95,8 @@ function ThumbnailImage({ isDeleted, onLoadFailure, onMeasure, + loadingIconSize, + loadingIndicatorStyles, }: ThumbnailImageProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -161,6 +170,8 @@ function ThumbnailImage({ }} isAuthTokenRequired={isAuthTokenRequired} objectPosition={objectPosition} + loadingIconSize={loadingIconSize} + loadingIndicatorStyles={loadingIndicatorStyles} /> diff --git a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx index 69692258e375..826e95ef28b3 100644 --- a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx +++ b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx @@ -58,7 +58,7 @@ function ReceiptCell({transactionItem, isSelected, style}: {transactionItem: Tra fallbackIconBackground={isSelected ? theme.buttonHoveredBG : undefined} iconSize="x-small" loadingIconSize="small" - loadingIndicatorStyles={styles.bgTransparent} + loadingIndicatorStyles={styles.receiptCellLoadingContainer} transactionItem={transactionItem} shouldUseInitialObjectPosition /> diff --git a/src/styles/index.ts b/src/styles/index.ts index 771aaaca6abe..ff09de410177 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5775,6 +5775,10 @@ const styles = (theme: ThemeColors) => minHeight: 200, }, + receiptCellLoadingContainer: { + backgroundColor: theme.activeComponentBG, + }, + wideRHPMoneyRequestReceiptViewContainer: { backgroundColor: theme.appBG, width: receiptPaneRHPWidth, From cf6194b7948537234240fed6f17ecce0fca2da3b Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 27 Aug 2025 18:12:54 +0200 Subject: [PATCH 21/56] fix image with loading name --- src/components/ImageWithLoading.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ImageWithLoading.tsx b/src/components/ImageWithLoading.tsx index c900b9abecf3..81291e89f441 100644 --- a/src/components/ImageWithLoading.tsx +++ b/src/components/ImageWithLoading.tsx @@ -23,7 +23,7 @@ type ImageWithSizeLoadingProps = { shouldShowOfflineIndicator?: boolean; } & ImageProps; -function ImageWithSizeCalculation({ +function ImageWithLoading({ onError, containerStyles, shouldShowOfflineIndicator = true, @@ -111,5 +111,5 @@ function ImageWithSizeCalculation({ ); } -ImageWithSizeCalculation.displayName = 'ImageWithSizeCalculation'; -export default React.memo(ImageWithSizeCalculation); +ImageWithLoading.displayName = 'ImageWithLoading'; +export default React.memo(ImageWithLoading); From 8a6da0e83bff3af4b1a7f92eb81cd31d11884b62 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 27 Aug 2025 18:22:11 +0200 Subject: [PATCH 22/56] implement borderless loader for receipt images --- src/components/ImageWithSizeCalculation.tsx | 5 +++++ src/components/ReceiptImage.tsx | 6 ++++++ .../ReportActionItem/MoneyRequestReceiptView.tsx | 7 ++++++- .../ReportActionItem/ReportActionItemImage.tsx | 10 +++++++++- src/components/ThumbnailImage.tsx | 5 +++++ src/styles/index.ts | 10 ++++++++++ 6 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index 939df5f504d0..59af932c72b7 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -43,6 +43,9 @@ type ImageWithSizeCalculationProps = { /** The style of the loading indicator */ loadingIndicatorStyles?: StyleProp; + + /** Callback to be called when the image loads */ + onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void; }; /** @@ -61,6 +64,7 @@ function ImageWithSizeCalculation({ objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, loadingIconSize, loadingIndicatorStyles, + onLoad, }: ImageWithSizeCalculationProps) { const styles = useThemeStyles(); @@ -85,6 +89,7 @@ function ImageWithSizeCalculation({ width: event.nativeEvent.width, height: event.nativeEvent.height, }); + onLoad?.(event); }} objectPosition={objectPosition} loadingIconSize={loadingIconSize} diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx index 10702b10dbe0..d3dfeb51b60b 100644 --- a/src/components/ReceiptImage.tsx +++ b/src/components/ReceiptImage.tsx @@ -108,6 +108,9 @@ type ReceiptImageProps = ( /** Whether the receipt empty state should extend to the full height of the container. */ shouldUseFullHeight?: boolean; + + /** Callback to be called when the image loads */ + onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void; }; function ReceiptImage({ @@ -134,6 +137,7 @@ function ReceiptImage({ shouldUseFullHeight, loadingIndicatorStyles, thumbnailContainerStyles, + onLoad, }: ReceiptImageProps) { const styles = useThemeStyles(); @@ -194,6 +198,7 @@ function ReceiptImage({ objectPosition={shouldUseInitialObjectPosition ? CONST.IMAGE_OBJECT_POSITION.INITIAL : CONST.IMAGE_OBJECT_POSITION.TOP} loadingIconSize={loadingIconSize} loadingIndicatorStyles={loadingIndicatorStyles} + onLoad={onLoad} /> ); } @@ -207,6 +212,7 @@ function ReceiptImage({ loadingIndicatorStyles={loadingIndicatorStyles} shouldShowOfflineIndicator={false} objectPosition={shouldUseInitialObjectPosition ? CONST.IMAGE_OBJECT_POSITION.INITIAL : CONST.IMAGE_OBJECT_POSITION.TOP} + onLoad={onLoad} /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index da38a08c8104..30093394c0c8 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -102,6 +102,8 @@ function MoneyRequestReceiptView({ canBeMissing: true, }); + const [isLoading, setIsLoading] = useState(true); + const parentReportAction = report?.parentReportActionID ? parentReportActions?.[report.parentReportActionID] : undefined; const isTrackExpense = isTrackExpenseReport(report); const moneyRequestReport = parentReport; @@ -224,6 +226,8 @@ function MoneyRequestReceiptView({ receiptStyle = shouldUseNarrowLayout ? styles.expenseViewImageSmall : styles.expenseViewImage; } + const showBorderlessLoading = isLoading && fillSpace; + // For empty receipt should be fullHeight // For the rest, expand to match the content return ( @@ -283,7 +287,7 @@ function MoneyRequestReceiptView({ contentContainerStyle={styles.flex1} > {hasReceipt && ( - + setIsLoading(false)} /> )} diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 0edba8a2d242..6ff4a8660385 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -75,6 +75,9 @@ type ReportActionItemImageProps = { /** Whether to use the thumbnail image instead of the full image */ shouldUseThumbnailImage?: boolean; + + /** Callback to be called when the image loads */ + onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void; }; /** @@ -102,6 +105,7 @@ function ReportActionItemImage({ shouldUseFullHeight, report: reportProp, shouldUseThumbnailImage, + onLoad, }: ReportActionItemImageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -185,7 +189,10 @@ function ReportActionItemImage({ accessibilityLabel={translate('accessibilityHints.viewAttachment')} accessibilityRole={CONST.ROLE.BUTTON} > - + )} @@ -197,6 +204,7 @@ function ReportActionItemImage({ {...propsObj} shouldUseFullHeight={shouldUseFullHeight} thumbnailContainerStyles={styles.thumbnailImageContainerHover} + onLoad={onLoad} /> ); } diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index 04076ba7a1fe..468ae027b768 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -72,6 +72,9 @@ type ThumbnailImageProps = { /** The style of the loading indicator */ loadingIndicatorStyles?: StyleProp; + + /** Callback to be called when the image loads */ + onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void; }; type UpdateImageSizeParams = { @@ -97,6 +100,7 @@ function ThumbnailImage({ onMeasure, loadingIconSize, loadingIndicatorStyles, + onLoad, }: ThumbnailImageProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -172,6 +176,7 @@ function ThumbnailImage({ objectPosition={objectPosition} loadingIconSize={loadingIconSize} loadingIndicatorStyles={loadingIndicatorStyles} + onLoad={onLoad} /> diff --git a/src/styles/index.ts b/src/styles/index.ts index ff09de410177..22d7d62d9ae0 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4761,6 +4761,16 @@ const styles = (theme: ThemeColors) => maxWidth: '100%', }, + getMoneyRequestViewImage: (showBorderless: boolean) => ({ + ...spacing.mh5, + overflow: 'hidden', + borderWidth: showBorderless ? 0 : 1, + borderColor: theme.border, + borderRadius: variables.componentBorderRadiusLarge, + height: 180, + maxWidth: '100%', + }), + expenseViewImage: { maxWidth: 360, aspectRatio: 16 / 9, From 6d387df0ea72ef59445092e5fc950ea5fcd94b00 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 1 Sep 2025 12:07:19 +0200 Subject: [PATCH 23/56] Add onLoad prop to EReceipt --- src/components/EReceipt.tsx | 16 ++++++++++++++-- src/components/EReceiptWithSizeCalculation.tsx | 3 +++ src/components/ReceiptEmptyState.tsx | 15 +++++++++++++-- src/components/ReceiptImage.tsx | 5 ++++- .../ReportActionItem/ReportActionItemImage.tsx | 2 +- 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/components/EReceipt.tsx b/src/components/EReceipt.tsx index b85ccd771f00..b4dd7fe31941 100644 --- a/src/components/EReceipt.tsx +++ b/src/components/EReceipt.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useRef} from 'react'; import {View} from 'react-native'; import useEReceipt from '@hooks/useEReceipt'; import useLocalize from '@hooks/useLocalize'; @@ -29,11 +29,14 @@ type EReceiptProps = { /** Where it is the preview */ isThumbnail?: boolean; + + /** Callback to be called when the image loads */ + onLoad?: () => void; }; const receiptMCCSize: number = variables.eReceiptMCCHeightWidthMedium; const backgroundImageMinWidth: number = variables.eReceiptBackgroundImageMinWidth; -function EReceipt({transactionID, transactionItem, isThumbnail = false}: EReceiptProps) { +function EReceipt({transactionID, transactionItem, onLoad, isThumbnail = false}: EReceiptProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); @@ -43,6 +46,8 @@ function EReceipt({transactionID, transactionItem, isThumbnail = false}: EReceip const {primaryColor, secondaryColor, titleColor, MCCIcon, tripIcon, backgroundImage} = useEReceipt(transactionItem ?? transaction); + const isLoadedRef = useRef(false); + const { amount: transactionAmount, currency: transactionCurrency, @@ -61,6 +66,13 @@ function EReceipt({transactionID, transactionItem, isThumbnail = false}: EReceip return ( { + if (isLoadedRef.current) { + return; + } + isLoadedRef.current = true; + onLoad?.(); + }} style={[ styles.eReceiptContainer, primaryColor ? StyleUtils.getBackgroundColorStyle(primaryColor) : undefined, diff --git a/src/components/EReceiptWithSizeCalculation.tsx b/src/components/EReceiptWithSizeCalculation.tsx index a910fa7498d9..9e824a5498fa 100644 --- a/src/components/EReceiptWithSizeCalculation.tsx +++ b/src/components/EReceiptWithSizeCalculation.tsx @@ -16,6 +16,9 @@ type EReceiptWithSizeCalculationProps = { /** Whether the eReceipt should preserve aspect ratio */ shouldUseAspectRatio?: boolean; + + /** Callback to be called when the image loads */ + onLoad?: () => void; }; const eReceiptAspectRatio = variables.eReceiptBGHWidth / variables.eReceiptBGHeight; diff --git a/src/components/ReceiptEmptyState.tsx b/src/components/ReceiptEmptyState.tsx index 7418966ede54..4b286971b378 100644 --- a/src/components/ReceiptEmptyState.tsx +++ b/src/components/ReceiptEmptyState.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useRef} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import useLocalize from '@hooks/useLocalize'; @@ -26,13 +26,17 @@ type ReceiptEmptyStateProps = { shouldUseFullHeight?: boolean; style?: StyleProp; + + /** Callback to be called when the image loads */ + onLoad?: () => void; }; // Returns an SVG icon indicating that the user should attach a receipt -function ReceiptEmptyState({onPress, disabled = false, isThumbnail = false, isInMoneyRequestView = false, shouldUseFullHeight = false, style}: ReceiptEmptyStateProps) { +function ReceiptEmptyState({onPress, disabled = false, isThumbnail = false, isInMoneyRequestView = false, shouldUseFullHeight = false, style, onLoad}: ReceiptEmptyStateProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const theme = useTheme(); + const isLoadedRef = useRef(false); const Wrapper = onPress ? PressableWithoutFeedback : View; const containerStyle = [ @@ -46,6 +50,13 @@ function ReceiptEmptyState({onPress, disabled = false, isThumbnail = false, isIn return ( { + if (isLoadedRef.current) { + return; + } + isLoadedRef.current = true; + onLoad?.(); + }} accessibilityRole="imagebutton" accessibilityLabel={translate('receipt.upload')} onPress={onPress} diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx index d3dfeb51b60b..9576a9a17967 100644 --- a/src/components/ReceiptImage.tsx +++ b/src/components/ReceiptImage.tsx @@ -110,7 +110,7 @@ type ReceiptImageProps = ( shouldUseFullHeight?: boolean; /** Callback to be called when the image loads */ - onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void; + onLoad?: (event?: {nativeEvent: {width: number; height: number}}) => void; }; function ReceiptImage({ @@ -148,6 +148,7 @@ function ReceiptImage({ onPress={onPress} disabled={!onPress} shouldUseFullHeight={shouldUseFullHeight} + onLoad={onLoad} /> ); } @@ -157,6 +158,7 @@ function ReceiptImage({ ); } @@ -166,6 +168,7 @@ function ReceiptImage({ ); } diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 6ff4a8660385..fd17567a1e63 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -77,7 +77,7 @@ type ReportActionItemImageProps = { shouldUseThumbnailImage?: boolean; /** Callback to be called when the image loads */ - onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void; + onLoad?: (event?: {nativeEvent: {width: number; height: number}}) => void; }; /** From eeea17c136cb44b298f6c46c6338dc154d59fe4d Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 4 Sep 2025 16:51:36 +0200 Subject: [PATCH 24/56] fix cropped ereceipt wide rhp --- src/components/ReceiptImage.tsx | 1 + src/components/ReportActionItem/ReportActionItemImage.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx index 9576a9a17967..3f010e66d536 100644 --- a/src/components/ReceiptImage.tsx +++ b/src/components/ReceiptImage.tsx @@ -168,6 +168,7 @@ function ReceiptImage({ ); diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index fd17567a1e63..0ffb3643b4e9 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -135,7 +135,7 @@ function ReportActionItemImage({ let propsObj: ReceiptImageProps; if (isEReceipt) { - propsObj = {isEReceipt: true, transactionID: transaction.transactionID, iconSize: isSingleImage ? 'medium' : ('small' as IconSize)}; + propsObj = {isEReceipt: true, transactionID: transaction.transactionID, iconSize: isSingleImage ? 'medium' : ('small' as IconSize), shouldUseFullHeight}; } else if (thumbnail && !isLocalFile) { propsObj = { shouldUseThumbnailImage: shouldUseThumbnailImage ?? true, From a9f8508165c84a60bd253cab380a60b42c5b5093 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Fri, 5 Sep 2025 16:36:53 +0200 Subject: [PATCH 25/56] remove unused style --- src/styles/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 22d7d62d9ae0..2496c59b41a8 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5802,10 +5802,6 @@ const styles = (theme: ThemeColors) => minHeight: '100%', }, - smallMoreButton: { - marginRight: 12, - }, - getTestToolsNavigatorOuterView: (shouldUseNarrowLayout: boolean) => ({ flex: 1, justifyContent: shouldUseNarrowLayout ? 'flex-end' : 'center', From a0a128f56626c0c223b96a42920c4336cba45e18 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 10 Sep 2025 17:14:04 +0200 Subject: [PATCH 26/56] improve comments v3 --- src/pages/home/ReportScreen.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index e23f60ad785e..e547aac78f65 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -3,6 +3,8 @@ import {useIsFocused} from '@react-navigation/native'; import {deepEqual} from 'fast-equals'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {FlatList, ViewStyle} from 'react-native'; +// We use Animated for all functionality related to wide RHP to make it easier +// to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. // eslint-disable-next-line no-restricted-imports import {Animated, DeviceEventEmitter, InteractionManager, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; From bde71b9504b81f9a2a20bceb6df67d86ed958b66 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 10 Sep 2025 17:17:16 +0200 Subject: [PATCH 27/56] fix after merge conflicts --- src/pages/home/ReportScreen.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index e547aac78f65..cfdba3aa2bf7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -893,6 +893,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { reportActions={reportActions} transactions={visibleTransactions} newTransactions={newTransactions} + violations={allReportViolations} hasOlderActions={hasOlderActions} hasNewerActions={hasNewerActions} showReportActionsLoadingState={showReportActionsLoadingState} From 8f1bfe9ae460426fb56fc73f125f9c4b54419eff Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 10 Sep 2025 17:29:26 +0200 Subject: [PATCH 28/56] fix typescript --- .../MoneyRequestReportTransactionList.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index b2929c338d00..08fa8ac638d4 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -219,7 +219,9 @@ function MoneyRequestReportTransactionList({ // to display prev/next arrows in RHP for navigation const sortedSiblingTransactionReportIDs = getThreadReportIDsForTransactions(reportActions, sortedTransactions); setActiveTransactionThreadIDs(sortedSiblingTransactionReportIDs).then(() => { - markReportIDAsExpense(reportIDToNavigate); + if (reportIDToNavigate) { + markReportIDAsExpense(reportIDToNavigate); + } Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(routeParams)); }); }, From 9d490d75fe11bafbc460fec1f5e68800576ee6f0 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 11 Sep 2025 12:09:58 +0200 Subject: [PATCH 29/56] check if id is defined --- .../MoneyRequestReportTransactionsNavigation.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx index 122b529d38ed..c4f2733ce54c 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx @@ -60,15 +60,17 @@ function MoneyRequestReportTransactionsNavigation({currentReportID}: MoneyReques onNext={(e) => { const backTo = Navigation.getActiveRoute(); e?.preventDefault(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - markReportIDAsExpense(nextReportID!); + if (nextReportID) { + markReportIDAsExpense(nextReportID); + } Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: nextReportID, backTo}), {forceReplace: true}); }} onPrevious={(e) => { const backTo = Navigation.getActiveRoute(); e?.preventDefault(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - markReportIDAsExpense(prevReportID!); + if (prevReportID) { + markReportIDAsExpense(prevReportID); + } Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: prevReportID, backTo}), {forceReplace: true}); }} /> From bd59e0a3839003ac8a7bf0d42f974d412855ba5b Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 11 Sep 2025 15:59:57 +0200 Subject: [PATCH 30/56] fix route type for ReportScreen --- src/components/ScreenWrapper/index.tsx | 4 ++-- src/pages/home/ReportScreen.tsx | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/ScreenWrapper/index.tsx b/src/components/ScreenWrapper/index.tsx index 1e7f6a67b929..1a9bdc14464e 100644 --- a/src/components/ScreenWrapper/index.tsx +++ b/src/components/ScreenWrapper/index.tsx @@ -19,7 +19,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; 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} from '@libs/Navigation/types'; +import type {ReportsSplitNavigatorParamList, RootNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; import {closeReactNativeApp} from '@userActions/HybridApp'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; @@ -47,7 +47,7 @@ type ScreenWrapperProps = Omit & * * This is required because transitionEnd event doesn't trigger in the testing environment. */ - navigation?: PlatformStackNavigationProp | PlatformStackNavigationProp; + navigation?: PlatformStackNavigationProp | PlatformStackNavigationProp | PlatformStackNavigationProp; /** A unique ID to find the screen wrapper in tests */ testID: string; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index cfdba3aa2bf7..929b353f865e 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -80,7 +80,7 @@ import { isValidReportIDFromPath, } from '@libs/ReportUtils'; import {isNumeric} from '@libs/ValidationUtils'; -import type {ReportsSplitNavigatorParamList} from '@navigation/types'; +import type {ReportsSplitNavigatorParamList, SearchReportParamList} from '@navigation/types'; import {setShouldShowComposeInput} from '@userActions/Composer'; import { clearDeleteTransactionNavigateBackUrl, @@ -106,7 +106,9 @@ import ReportFooter from './report/ReportFooter'; import type {ActionListContextType, ScrollPosition} from './ReportScreenContext'; import {ActionListContext} from './ReportScreenContext'; -type ReportScreenNavigationProps = PlatformStackScreenProps; +type ReportScreenNavigationProps = + | PlatformStackScreenProps + | PlatformStackScreenProps; type ReportScreenProps = ReportScreenNavigationProps; @@ -194,7 +196,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { return; } - const lastAccessedReportID = findLastAccessedReport(!isBetaEnabled(CONST.BETAS.DEFAULT_ROOMS), !!route.params.openOnAdminRoom)?.reportID; + const lastAccessedReportID = findLastAccessedReport(!isBetaEnabled(CONST.BETAS.DEFAULT_ROOMS), 'openOnAdminRoom' in route.params && !!route.params.openOnAdminRoom)?.reportID; // It's possible that reports aren't fully loaded yet // in that case the reportID is undefined @@ -798,7 +800,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const {isSmallScreenWidth} = useResponsiveLayout(); const shouldShowWideRHP = - // @ts-expect-error @TODO-RHP somebody messed up the types route.name === SCREENS.SEARCH.REPORT_RHP && !isSmallScreenWidth && (isTransactionThread(parentReportAction) || parentReportAction?.childType === CONST.REPORT.TYPE.EXPENSE || parentReportAction?.childType === CONST.REPORT.TYPE.IOU); From 05bebe9ccab822557cb8d91d0fd03bb455da150e Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 11 Sep 2025 16:08:02 +0200 Subject: [PATCH 31/56] improve readability --- src/components/ReportActionItem/ReportActionItemImage.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 0ffb3643b4e9..c0258966d0e5 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -131,6 +131,7 @@ function ReportActionItemImage({ const originalImageSource = tryResolveUrlFromApiRoot(image ?? ''); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail ?? ''); const isEReceipt = transaction && !hasReceiptSource(transaction) && hasEReceipt(transaction); + const isPDF = filename && Str.isPDF(filename); let propsObj: ReceiptImageProps; @@ -141,8 +142,9 @@ function ReportActionItemImage({ shouldUseThumbnailImage: shouldUseThumbnailImage ?? true, // PDF won't have originalImage that we can use. Use thumbnail instead + // We explicitly want to use || instead of nullish-coalescing because shouldUseThumbnailImage can be false. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - source: shouldUseThumbnailImage || (filename && Str.isPDF(filename)) ? thumbnailSource : originalImageSource, + source: shouldUseThumbnailImage || isPDF ? thumbnailSource : originalImageSource, fallbackIcon: Expensicons.Receipt, fallbackIconSize: isSingleImage ? variables.iconSizeSuperLarge : variables.iconSizeExtraLarge, isAuthTokenRequired: true, @@ -150,7 +152,7 @@ function ReportActionItemImage({ // If the image is full height, use initial position to make sure it will grow properly to fill the container shouldUseInitialObjectPosition: isMapDistanceRequest && !shouldUseFullHeight, }; - } else if (isLocalFile && filename && Str.isPDF(filename) && typeof originalImageSource === 'string') { + } else if (isLocalFile && isPDF && typeof originalImageSource === 'string') { propsObj = {isPDFThumbnail: true, source: originalImageSource}; } else { propsObj = { From ac6250c5a2565d515c2bd279edb6c0ace34eada0 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 11 Sep 2025 17:46:06 +0200 Subject: [PATCH 32/56] apply change requests --- src/components/MoneyReportHeader.tsx | 6 +++--- src/components/MoneyRequestHeader.tsx | 6 +++--- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 46103a0fc14c..47d9403b32de 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -311,7 +311,7 @@ function MoneyReportHeader({ const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey} = useSearchContext(); const {wideRHPRouteKeys} = useContext(WideRHPContext); - const shouldDisplayWideRHPVersion = wideRHPRouteKeys.length > 0 && !isSmallScreenWidth; + const shouldDisplayNarrowMoreButton = !shouldDisplayNarrowVersion || (wideRHPRouteKeys.length > 0 && !isSmallScreenWidth); const beginExportWithTemplate = useCallback( (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => { @@ -1236,7 +1236,7 @@ function MoneyReportHeader({ shouldShowBorderBottom={false} shouldEnableDetailPageNavigation > - {(!shouldDisplayNarrowVersion || shouldDisplayWideRHPVersion) && ( + {shouldDisplayNarrowMoreButton && ( {!!primaryAction && !shouldShowSelectedTransactionsButton && primaryActionsImplementation[primaryAction]} {!!applicableSecondaryActions.length && !shouldShowSelectedTransactionsButton && KYCMoreDropdown} @@ -1254,7 +1254,7 @@ function MoneyReportHeader({ )} - {shouldDisplayNarrowVersion && !shouldShowSelectedTransactionsButton && !shouldDisplayWideRHPVersion && ( + {!shouldDisplayNarrowMoreButton && !shouldShowSelectedTransactionsButton && ( {!!primaryAction && {primaryActionsImplementation[primaryAction]}} {!!applicableSecondaryActions.length && KYCMoreDropdown} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 15973284e32c..8f706a5f0c89 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -280,7 +280,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }; const applicableSecondaryActions = secondaryActions.map((action) => secondaryActionsImplementation[action]); - const shouldDisplayWideRHPVersion = wideRHPRouteKeys.length > 0 && !isSmallScreenWidth; + const shouldDisplayNarrowMoreButton = !shouldUseNarrowLayout || (wideRHPRouteKeys.length > 0 && !isSmallScreenWidth); return ( @@ -304,7 +304,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre shouldEnableDetailPageNavigation openParentReportInCurrentTab={shouldOpenParentReportInCurrentTab} > - {(!shouldUseNarrowLayout || shouldDisplayWideRHPVersion) && ( + {shouldDisplayNarrowMoreButton && ( {!!primaryAction && primaryActionImplementation[primaryAction]} {!!applicableSecondaryActions.length && ( @@ -321,7 +321,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre )} {shouldDisplayTransactionNavigation && } - {shouldUseNarrowLayout && !shouldDisplayWideRHPVersion && ( + {!shouldDisplayNarrowMoreButton && ( {!!primaryAction && {primaryActionImplementation[primaryAction]}} {!!applicableSecondaryActions.length && ( diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 30093394c0c8..59a8c2c7e88a 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -231,7 +231,7 @@ function MoneyRequestReceiptView({ // For empty receipt should be fullHeight // For the rest, expand to match the content return ( - + {shouldShowReceiptAudit && ( Date: Fri, 12 Sep 2025 12:16:36 +0200 Subject: [PATCH 33/56] improve onLoad --- src/components/EReceipt.tsx | 17 +++++++++-------- src/components/ReceiptEmptyState.tsx | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/components/EReceipt.tsx b/src/components/EReceipt.tsx index b4dd7fe31941..bbadd65901ad 100644 --- a/src/components/EReceipt.tsx +++ b/src/components/EReceipt.tsx @@ -1,4 +1,4 @@ -import React, {useRef} from 'react'; +import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import useEReceipt from '@hooks/useEReceipt'; import useLocalize from '@hooks/useLocalize'; @@ -64,15 +64,16 @@ function EReceipt({transactionID, transactionItem, onLoad, isThumbnail = false}: const primaryTextColorStyle = primaryColor ? StyleUtils.getColorStyle(primaryColor) : undefined; const titleTextColorStyle = titleColor ? StyleUtils.getColorStyle(titleColor) : undefined; + useEffect(() => { + if (isLoadedRef.current) { + return; + } + isLoadedRef.current = true; + onLoad?.(); + }, [onLoad]); + return ( { - if (isLoadedRef.current) { - return; - } - isLoadedRef.current = true; - onLoad?.(); - }} style={[ styles.eReceiptContainer, primaryColor ? StyleUtils.getBackgroundColorStyle(primaryColor) : undefined, diff --git a/src/components/ReceiptEmptyState.tsx b/src/components/ReceiptEmptyState.tsx index 4b286971b378..2592d8b09547 100644 --- a/src/components/ReceiptEmptyState.tsx +++ b/src/components/ReceiptEmptyState.tsx @@ -1,4 +1,4 @@ -import React, {useRef} from 'react'; +import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import useLocalize from '@hooks/useLocalize'; @@ -48,15 +48,16 @@ function ReceiptEmptyState({onPress, disabled = false, isThumbnail = false, isIn style, ]; + useEffect(() => { + if (isLoadedRef.current) { + return; + } + isLoadedRef.current = true; + onLoad?.(); + }, [onLoad]); + return ( { - if (isLoadedRef.current) { - return; - } - isLoadedRef.current = true; - onLoad?.(); - }} accessibilityRole="imagebutton" accessibilityLabel={translate('receipt.upload')} onPress={onPress} From 662f8f66408984f876aaaf9da03f14a59241191d Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 17 Sep 2025 12:35:58 +0200 Subject: [PATCH 34/56] improve handling many RHPs --- .../WideRHPContextProvider/index.tsx | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/src/components/WideRHPContextProvider/index.tsx b/src/components/WideRHPContextProvider/index.tsx index 0835f4974aac..de1f64efa2c2 100644 --- a/src/components/WideRHPContextProvider/index.tsx +++ b/src/components/WideRHPContextProvider/index.tsx @@ -1,4 +1,5 @@ import {findFocusedRoute, StackActions, useNavigation, useRoute} from '@react-navigation/native'; +import type {NavigationState, PartialState} from '@react-navigation/native'; import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; // We use Animated for all functionality related to wide RHP to make it easier // to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. @@ -19,6 +20,41 @@ const secondOverlayProgress = new Animated.Value(0); const wideRHPMaxWidth = variables.receiptPaneRHPMaxWidth + variables.sideBarWidth; +/** + * Utility function that extracts all unique navigation keys from a React Navigation state. + * Recursively traverses the navigation state tree and collects all route keys. + * + * @param state - The React Navigation state (can be partial or complete) + * @returns Set of unique route keys found in the navigation state + */ +function extractNavigationKeys(state: NavigationState | PartialState | undefined): Set { + if (!state || !state.routes) { + return new Set(); + } + + const keys = new Set(); + const routesToProcess = [...state.routes]; + + while (routesToProcess.length > 0) { + const route = routesToProcess.pop(); + if (!route) { + continue; + } + + // Add the current route key to the set + if (route.key) { + keys.add(route.key); + } + + // If the route has a nested state, add its routes to the processing queue + if (route.state && 'routes' in route.state && Array.isArray(route.state.routes)) { + routesToProcess.push(...route.state.routes); + } + } + + return keys; +} + /** * Calculates the optimal width for the receipt pane RHP based on window width. * Ensures the RHP doesn't exceed maximum width and maintains minimum responsive width. @@ -38,10 +74,42 @@ const receiptPaneRHPWidth = new Animated.Value(calculateReceiptPaneRHPWidth(Dime const WideRHPContext = createContext(defaultWideRHPContextValue); function WideRHPContextProvider({children}: React.PropsWithChildren) { - const [wideRHPRouteKeys, setWideRHPRouteKeys] = useState([]); + // We have a separate containers for allWideRHPRouteKeys and wideRHPRouteKeys because we may have two or more RHPs on the stack. + // For convenience and proper overlay logic wideRHPRouteKeys will show only the keys existing in the last RHP. + const [allWideRHPRouteKeys, setAllWideRHPRouteKeys] = useState([]); const [shouldRenderSecondaryOverlay, setShouldRenderSecondaryOverlay] = useState(false); const [expenseReportIDs, setExpenseReportIDs] = useState>(new Set()); + // Return undefined if RHP is not the last route + const lastRHPRouteKey = useRootNavigationState((state) => { + const lastRoute = state?.routes.at(-1); + + if (!lastRoute) { + return undefined; + } + + return lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR ? lastRoute.key : undefined; + }); + + const wideRHPRouteKeys = useMemo(() => { + const rootState = navigationRef.getRootState(); + + if (!rootState) { + return []; + } + + const lastRHPRoute = rootState.routes.find((route) => route.key === lastRHPRouteKey); + + if (!lastRHPRoute) { + return []; + } + + const lastRHPKeys = extractNavigationKeys(lastRHPRoute.state); + const currentKeys = allWideRHPRouteKeys.filter((key) => lastRHPKeys.has(key)); + + return currentKeys; + }, [allWideRHPRouteKeys, lastRHPRouteKey]); + /** * Determines whether the secondary overlay should be displayed. * Shows second overlay when RHP is open and there is a wide RHP route open but there is another regular route on the top. @@ -75,7 +143,7 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { const newKey = route.key; // If the key is in the array, don't add it. - setWideRHPRouteKeys((prev) => (prev.includes(newKey) ? prev : [newKey, ...prev])); + setAllWideRHPRouteKeys((prev) => (prev.includes(newKey) ? prev : [newKey, ...prev])); }, []); /** @@ -91,13 +159,13 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { const keyToRemove = route.key; // Do nothing, the key is not here - if (!wideRHPRouteKeys.includes(keyToRemove)) { + if (!allWideRHPRouteKeys.includes(keyToRemove)) { return; } - setWideRHPRouteKeys((prev) => prev.filter((key) => key !== keyToRemove)); + setAllWideRHPRouteKeys((prev) => (prev.includes(keyToRemove) ? prev.filter((key) => key !== keyToRemove) : prev)); }, - [wideRHPRouteKeys], + [allWideRHPRouteKeys], ); /** @@ -262,4 +330,4 @@ WideRHPContextProvider.displayName = 'WideRHPContextProvider'; export default WideRHPContextProvider; -export {expandedRHPProgress, receiptPaneRHPWidth, secondOverlayProgress, useShowWideRHPVersion, WideRHPContext}; +export {expandedRHPProgress, receiptPaneRHPWidth, secondOverlayProgress, useShowWideRHPVersion, WideRHPContext, extractNavigationKeys}; From 8e0f1ae5dc0d930d08660a2b10a277093ead88fa Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 17 Sep 2025 13:57:09 +0200 Subject: [PATCH 35/56] fix marking task as expense --- src/components/Search/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index a846dd9f6ca0..1b8a0a735d31 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -655,6 +655,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS } const isFromSelfDM = item.reportID === CONST.REPORT.UNREPORTED_REPORT_ID; + const isTask = type === CONST.SEARCH.DATA_TYPES.TASK; const reportID = isTransactionItem && (!item.isFromOneTransactionReport || isFromSelfDM) && item.transactionThreadReportID !== CONST.REPORT.UNREPORTED_REPORT_ID @@ -679,11 +680,13 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS return; } - markReportIDAsExpense(reportID); + if (!isTask) { + markReportIDAsExpense(reportID); + } Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo})); }, - [isMobileSelectionModeEnabled, toggleTransaction, queryJSON, handleSearch, searchKey, reportActionsArray, hash, markReportIDAsExpense], + [isMobileSelectionModeEnabled, type, toggleTransaction, reportActionsArray, hash, queryJSON, handleSearch, searchKey, markReportIDAsExpense], ); const currentColumns = useMemo(() => { From 5b2fd65c9f1ddb8f7b6e706b9ef3e3097887f642 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 17 Sep 2025 14:25:10 +0200 Subject: [PATCH 36/56] stick audit message to receipt image --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 59a8c2c7e88a..a990be668c21 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -307,10 +307,14 @@ function MoneyRequestReceiptView({ /> )} + {!!shouldShowAuditMessage && hasReceipt && ( + + + + )} )} {!shouldShowReceiptEmptyState && !hasReceipt && } - {!!shouldShowAuditMessage && } { From 04e86e790ec18fc78cd9598a01f6913dc84e5796 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 18 Sep 2025 10:23:38 +0200 Subject: [PATCH 37/56] Fix navigation theme on narrow layout --- src/libs/Navigation/NavigationRoot.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 4d38ea66859c..dc5c3a2c385a 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -6,6 +6,7 @@ import useCurrentReportID from '@hooks/useCurrentReportID'; import useOnyx from '@hooks/useOnyx'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; import useThemePreference from '@hooks/useThemePreference'; import Firebase from '@libs/Firebase'; import FS from '@libs/Fullstory'; @@ -87,6 +88,7 @@ function parseAndLogRoute(state: NavigationState) { function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: NavigationRootProps) { const firstRenderRef = useRef(true); const themePreference = useThemePreference(); + const theme = useTheme(); const {cleanStaleScrollOffsets} = useContext(ScrollOffsetContext); const currentReportIDValue = useCurrentReportID(); @@ -172,16 +174,16 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N colors: { ...defaultNavigationTheme.colors, /** - * We want to have a stack with variable size of screens in RHP. - * The stack is the size of the biggest screen in RHP. Screens that should be smaller will reduce it's size with margin. - * The stack have to be this size because it have a container with overflow: hidden. - * background: transparent is used to make bottom of the card stack transparent. - * To make sure that everything is correct, we will use theme.appBG in the screen wrapper. + * We want to have a stack with variable size of screens in RHP (wide layout). + * The stack is the size of the biggest screen in RHP. Screens that should be smaller will reduce its size with margin. + * The stack has to be this size because it has a container with overflow: hidden. + * On wide layout, background: 'transparent' is used to make the bottom of the card stack transparent. + * On narrow layout, we use theme.appBG to match the standard app background. */ - background: 'transparent', + background: shouldUseNarrowLayout ? theme.appBG : 'transparent', }, }; - }, [themePreference]); + }, [shouldUseNarrowLayout, theme.appBG, themePreference]); useEffect(() => { if (firstRenderRef.current) { From f973af531220d665f0d0d79d1c969206551bcfa2 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Sep 2025 16:20:00 +0200 Subject: [PATCH 38/56] Add shouldCalculateAspectRatioForWideImage prop to Image component --- src/components/Image/index.tsx | 6 ++++-- src/components/Image/types.ts | 3 +++ src/components/ReceiptImage.tsx | 1 + src/components/ReportActionItem/ReportActionItemImage.tsx | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index 35107533269b..fb5e796764d0 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -10,6 +10,7 @@ import type {ImageOnLoadEvent, ImageProps} from './types'; function Image({ source: propsSource, + shouldCalculateAspectRatioForWideImage = false, isAuthTokenRequired = false, onLoad, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, @@ -30,14 +31,14 @@ function Image({ return; } - if (width > height) { + if (width > height && !shouldCalculateAspectRatioForWideImage) { setAspectRatio(1); return; } setAspectRatio(height ? width / height : 'auto'); }, - [isObjectPositionTop], + [isObjectPositionTop, shouldCalculateAspectRatioForWideImage], ); const handleLoad = useCallback( @@ -146,6 +147,7 @@ function Image({ /> ); } + return ( ); } diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index c0258966d0e5..0297d8441928 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -194,6 +194,7 @@ function ReportActionItemImage({ )} From d707096b880adf0bb9467447d6f94afea7cda235 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 18 Sep 2025 22:27:08 +0200 Subject: [PATCH 39/56] fix wrong spacing receipt audit message --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index a990be668c21..9643618b2531 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -243,7 +243,7 @@ function MoneyRequestReceiptView({ {shouldShowReceiptEmptyState && ( {hasReceipt && ( From 912109814b64131fec203117fcaabb5f5651a11d Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 19 Sep 2025 10:20:54 +0200 Subject: [PATCH 40/56] Fix reading receipt pane rhp width in overlayStyles --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 7503ac91315e..0444118a704f 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5375,7 +5375,7 @@ const dynamicStyles = (theme: ThemeColors) => ({ ...positioning.pFixed, // We need to stretch the overlay to cover the sidebar and the translate animation distance. - left: hasMarginLeft ? variables.receiptPaneRHPMaxWidth : -2 * variables.sideBarWidth, + left: hasMarginLeft ? receiptPaneRHPWidth : -2 * variables.sideBarWidth, top: 0, bottom: 0, right: hasMarginRight ? variables.sideBarWidth : 0, From f179e9d1d66769aeb0c67d4e5f775954ba98246f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Fri, 19 Sep 2025 13:18:46 +0200 Subject: [PATCH 41/56] fix empty scrollbar issue --- src/pages/home/ReportScreen.tsx | 5 ++++- src/styles/index.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 48873c89c577..effee180c990 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -863,7 +863,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) { {shouldShowWideRHP && ( - + borderColor: theme.border, }, - wideRHPMoneyRequestReceiptViewScrollView: { + wideRHPMoneyRequestReceiptViewScrollViewContainer: { paddingTop: 12, minHeight: '100%', }, + wideRHPMoneyRequestReceiptViewScrollView: { + scrollbarGutter: 'stable both-edges' + }, }) satisfies StaticStyles; const dynamicStyles = (theme: ThemeColors) => From 181cfbbcde26e60d19ae2878ac574d29cb7375be Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Fri, 19 Sep 2025 14:00:12 +0200 Subject: [PATCH 42/56] fix help pane with wide rhp --- .../Navigators/Overlay/BaseOverlay.tsx | 4 +++- src/styles/index.ts | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx b/src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx index 10dc5d9afa18..d2a2ef8047c0 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx @@ -4,6 +4,7 @@ import React from 'react'; import {Animated, View} from 'react-native'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import useLocalize from '@hooks/useLocalize'; +import useSidePanel from '@hooks/useSidePanel'; import useThemeStyles from '@hooks/useThemeStyles'; import type {OverlayStylesParams} from '@styles/index'; import CONST from '@src/CONST'; @@ -26,11 +27,12 @@ function BaseOverlay({onPress, hasMarginRight = false, progress, hasMarginLeft = const styles = useThemeStyles(); const {current} = useCardAnimation(); const {translate} = useLocalize(); + const {sidePanelTranslateX} = useSidePanel(); return ( {/* In the latest Electron version buttons can't be both clickable and draggable. diff --git a/src/styles/index.ts b/src/styles/index.ts index 3f73a1444ca8..695df88d56f8 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import type {LineLayerStyleProps} from '@rnmapbox/maps/src/utils/MapboxStyles'; import lodashClamp from 'lodash/clamp'; +import type {RefObject} from 'react'; import type {LineLayer} from 'react-map-gl'; import type {ImageStyle, TextStyle, ViewStyle} from 'react-native'; // eslint-disable-next-line no-restricted-imports @@ -5374,14 +5375,24 @@ const dynamicStyles = (theme: ThemeColors) => vertical: windowHeight - CONST.MENU_POSITION_REPORT_ACTION_COMPOSE_BOTTOM, }) satisfies AnchorPosition, - overlayStyles: ({progress, hasMarginRight = false, hasMarginLeft = false}: {progress: OverlayStylesParams; hasMarginRight?: boolean; hasMarginLeft?: boolean}) => + overlayStyles: ({ + progress, + hasMarginRight = false, + hasMarginLeft = false, + sidePanelTranslateX, + }: { + progress: OverlayStylesParams; + hasMarginRight?: boolean; + hasMarginLeft?: boolean; + sidePanelTranslateX?: RefObject; + }) => ({ ...positioning.pFixed, // We need to stretch the overlay to cover the sidebar and the translate animation distance. left: hasMarginLeft ? receiptPaneRHPWidth : -2 * variables.sideBarWidth, top: 0, bottom: 0, - right: hasMarginRight ? variables.sideBarWidth : 0, + right: hasMarginRight ? Animated.add(variables.sideBarWidth, sidePanelTranslateX ? Animated.subtract(variables.sideBarWidth, sidePanelTranslateX.current) : 0) : 0, backgroundColor: theme.overlay, opacity: progress.interpolate({ inputRange: [0, 1], From 218733e9d65a8b3424ebe153c33e54b4b3177620 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Fri, 19 Sep 2025 14:05:27 +0200 Subject: [PATCH 43/56] fix prettier --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 695df88d56f8..4755fc066394 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5220,7 +5220,7 @@ const staticStyles = (theme: ThemeColors) => minHeight: '100%', }, wideRHPMoneyRequestReceiptViewScrollView: { - scrollbarGutter: 'stable both-edges' + scrollbarGutter: 'stable both-edges', }, }) satisfies StaticStyles; From bdd55b94b39aa1cc1f7492e465f39f74e0763c1d Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 22 Sep 2025 12:26:10 +0200 Subject: [PATCH 44/56] fix condition for report screen wide screen --- src/pages/home/ReportScreen.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 02e1835d4e34..4941725757f9 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -803,7 +803,11 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const shouldShowWideRHP = route.name === SCREENS.SEARCH.REPORT_RHP && !isSmallScreenWidth && - (isTransactionThread(parentReportAction) || parentReportAction?.childType === CONST.REPORT.TYPE.EXPENSE || parentReportAction?.childType === CONST.REPORT.TYPE.IOU); + (isTransactionThread(parentReportAction) || + parentReportAction?.childType === CONST.REPORT.TYPE.EXPENSE || + parentReportAction?.childType === CONST.REPORT.TYPE.IOU || + report?.type === CONST.REPORT.TYPE.EXPENSE || + report?.type === CONST.REPORT.TYPE.IOU); useShowWideRHPVersion(shouldShowWideRHP); From f3c9fb9ecaa4e94b3a626d610779b4e16e9605a0 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 23 Sep 2025 13:57:46 +0200 Subject: [PATCH 45/56] fix last visible rhp route condition --- src/components/WideRHPContextProvider/index.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/WideRHPContextProvider/index.tsx b/src/components/WideRHPContextProvider/index.tsx index de1f64efa2c2..ef708c750e1b 100644 --- a/src/components/WideRHPContextProvider/index.tsx +++ b/src/components/WideRHPContextProvider/index.tsx @@ -6,6 +6,7 @@ import React, {createContext, useCallback, useContext, useEffect, useMemo, useSt // eslint-disable-next-line no-restricted-imports import {Animated, Dimensions, InteractionManager} from 'react-native'; import useRootNavigationState from '@hooks/useRootNavigationState'; +import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName'; import navigationRef from '@libs/Navigation/navigationRef'; import type {NavigationRoute} from '@libs/Navigation/types'; import variables from '@styles/variables'; @@ -81,14 +82,16 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { const [expenseReportIDs, setExpenseReportIDs] = useState>(new Set()); // Return undefined if RHP is not the last route - const lastRHPRouteKey = useRootNavigationState((state) => { - const lastRoute = state?.routes.at(-1); + const lastVisibleRHPRouteKey = useRootNavigationState((state) => { + const lastFullScreenRouteIndex = state?.routes.findLastIndex((route) => isFullScreenName(route.name)); + const lastRHPRouteIndex = state?.routes.findLastIndex((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR); - if (!lastRoute) { + // Both routes have to be present and the RHP have to be after last full screen for it to be visible. + if (lastFullScreenRouteIndex === -1 || lastRHPRouteIndex === -1 || lastFullScreenRouteIndex > lastRHPRouteIndex) { return undefined; } - return lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR ? lastRoute.key : undefined; + return state?.routes.at(lastRHPRouteIndex)?.key; }); const wideRHPRouteKeys = useMemo(() => { @@ -98,7 +101,7 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { return []; } - const lastRHPRoute = rootState.routes.find((route) => route.key === lastRHPRouteKey); + const lastRHPRoute = rootState.routes.find((route) => route.key === lastVisibleRHPRouteKey); if (!lastRHPRoute) { return []; @@ -108,7 +111,7 @@ function WideRHPContextProvider({children}: React.PropsWithChildren) { const currentKeys = allWideRHPRouteKeys.filter((key) => lastRHPKeys.has(key)); return currentKeys; - }, [allWideRHPRouteKeys, lastRHPRouteKey]); + }, [allWideRHPRouteKeys, lastVisibleRHPRouteKey]); /** * Determines whether the secondary overlay should be displayed. From ca452103ebd733a45d06eca1f504af28858e6b9a Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 25 Sep 2025 15:55:38 +0200 Subject: [PATCH 46/56] Calculate receipt image size without using aspectRatio --- src/components/Image/index.tsx | 20 ++++++++++++++++++-- src/components/Image/types.ts | 3 +++ src/components/ImageWithLoading.tsx | 10 +++++++++- src/components/ReceiptImage.tsx | 14 +++++++++++++- src/pages/home/ReportScreen.tsx | 5 +---- src/styles/index.ts | 3 --- 6 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index fb5e796764d0..9931602b6c1d 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -17,6 +17,7 @@ function Image({ style, loadingIconSize, loadingIndicatorStyles, + imageWidthToCalculateHeight, ...forwardedProps }: ImageProps) { const [aspectRatio, setAspectRatio] = useState(null); @@ -25,6 +26,21 @@ function Image({ const {shouldSetAspectRatioInStyle} = useContext(ImageBehaviorContext); + const aspectRatioStyle = useMemo(() => { + if (!shouldSetAspectRatioInStyle || !aspectRatio) { + return {}; + } + + if (!!imageWidthToCalculateHeight && typeof aspectRatio === 'number') { + return { + width: '100%', + height: imageWidthToCalculateHeight / aspectRatio, + }; + } + + return {aspectRatio, height: 'auto'}; + }, [shouldSetAspectRatioInStyle, aspectRatio, imageWidthToCalculateHeight]); + const updateAspectRatio = useCallback( (width: number, height: number) => { if (!isObjectPositionTop) { @@ -153,14 +169,14 @@ function Image({ // eslint-disable-next-line react/jsx-props-no-spreading {...forwardedProps} onLoad={handleLoad} - style={[style, shouldSetAspectRatioInStyle && aspectRatio ? {aspectRatio, height: 'auto'} : {}, shouldOpacityBeZero && {opacity: 0}]} + style={[style, aspectRatioStyle, shouldOpacityBeZero && {opacity: 0}]} source={source} /> ); } function imagePropsAreEqual(prevProps: ImageProps, nextProps: ImageProps) { - return prevProps.source === nextProps.source; + return prevProps.source === nextProps.source && prevProps.imageWidthToCalculateHeight === nextProps.imageWidthToCalculateHeight; } const ImageWithOnyx = React.memo(Image, imagePropsAreEqual); diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts index 4609d1c00736..7a57250b9848 100644 --- a/src/components/Image/types.ts +++ b/src/components/Image/types.ts @@ -66,6 +66,9 @@ type ImageOwnProps = BaseImageProps & { * cf https://github.com/Expensify/App/issues/51888 */ waitForSession?: () => void; + + /** If you want to calculate the image height dynamically instead of using aspectRatio, pass the width in this property */ + imageWidthToCalculateHeight?: number; }; type ImageProps = ImageOwnProps; diff --git a/src/components/ImageWithLoading.tsx b/src/components/ImageWithLoading.tsx index 81291e89f441..3999227a59b8 100644 --- a/src/components/ImageWithLoading.tsx +++ b/src/components/ImageWithLoading.tsx @@ -2,6 +2,7 @@ import delay from 'lodash/delay'; import React, {useEffect, useRef, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; +import {LayoutChangeEvent} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import AttachmentOfflineIndicator from './AttachmentOfflineIndicator'; @@ -21,6 +22,9 @@ type ImageWithSizeLoadingProps = { /** Whether to show offline indicator */ shouldShowOfflineIndicator?: boolean; + + /** Invoked on mount and layout changes */ + onLayout?: (event: LayoutChangeEvent) => void; } & ImageProps; function ImageWithLoading({ @@ -32,6 +36,7 @@ function ImageWithLoading({ loadingIndicatorStyles, resizeMode, onLoad, + onLayout, ...rest }: ImageWithSizeLoadingProps) { const styles = useThemeStyles(); @@ -74,7 +79,10 @@ function ImageWithLoading({ }, [isLoading]); return ( - + (undefined); + const lastUpdateWidthTimestampRef = useRef(new Date().getTime()); if (isEmptyReceipt) { return ( @@ -209,6 +214,12 @@ function ReceiptImage({ return ( { + if (e.nativeEvent.layout.width !== receiptImageWidth && e.timeStamp - lastUpdateWidthTimestampRef.current > MIN_UPDATE_WIDTH_DIFF) { + setReceiptImageWidth(e.nativeEvent.layout.width); + } + lastUpdateWidthTimestampRef.current = e.timeStamp; + }} source={{uri: source}} style={[style ?? [styles.w100, styles.h100], styles.overflowHidden]} isAuthTokenRequired={!!isAuthTokenRequired} @@ -218,6 +229,7 @@ function ReceiptImage({ objectPosition={shouldUseInitialObjectPosition ? CONST.IMAGE_OBJECT_POSITION.INITIAL : CONST.IMAGE_OBJECT_POSITION.TOP} onLoad={onLoad} shouldCalculateAspectRatioForWideImage={shouldUseFullHeight} + imageWidthToCalculateHeight={receiptImageWidth} /> ); } diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 6a10d63d604c..af39dfe47995 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -891,10 +891,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { {shouldShowWideRHP && ( - + paddingTop: 12, minHeight: '100%', }, - wideRHPMoneyRequestReceiptViewScrollView: { - scrollbarGutter: 'stable both-edges', - }, }) satisfies StaticStyles; const dynamicStyles = (theme: ThemeColors) => From 0ea752800f507fecb6552f314a73c29d1ccfa710 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 26 Sep 2025 08:44:39 +0200 Subject: [PATCH 47/56] Fix showing loading indicator --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 9643618b2531..9245c487bcdd 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -287,7 +287,7 @@ function MoneyRequestReceiptView({ contentContainerStyle={styles.flex1} > {hasReceipt && ( - + Date: Fri, 26 Sep 2025 09:22:25 +0200 Subject: [PATCH 48/56] Fix imports in ImageWithLoading --- src/components/ImageWithLoading.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ImageWithLoading.tsx b/src/components/ImageWithLoading.tsx index 3999227a59b8..d72184121850 100644 --- a/src/components/ImageWithLoading.tsx +++ b/src/components/ImageWithLoading.tsx @@ -1,8 +1,7 @@ import delay from 'lodash/delay'; import React, {useEffect, useRef, useState} from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; +import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import {LayoutChangeEvent} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import AttachmentOfflineIndicator from './AttachmentOfflineIndicator'; From 1503e99364f0b228b3c140fbd912b23d5a9f6b17 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 26 Sep 2025 14:13:58 +0200 Subject: [PATCH 49/56] Prevent showing empty receipt before loading --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 9245c487bcdd..238438445162 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -154,7 +154,7 @@ function MoneyRequestReceiptView({ const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => (pendingAction ? undefined : transaction?.pendingFields?.[fieldPath]); const isReceiptAllowed = !isPaidReport && !isInvoice; - const shouldShowReceiptEmptyState = isReceiptAllowed && !hasReceipt; + const shouldShowReceiptEmptyState = isReceiptAllowed && !hasReceipt && !!transaction && isLoading; const [receiptImageViolations, receiptViolations] = useMemo(() => { const imageViolations = []; const allViolations = []; From 463114092155e1021bd4069549967e3afd3eee3b Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 30 Sep 2025 09:02:53 +0200 Subject: [PATCH 50/56] Remove styles.fullHeight --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- src/styles/index.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 238438445162..7e6bed278609 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -219,7 +219,7 @@ function MoneyRequestReceiptView({ let receiptStyle: StyleProp; if (fillSpace && shouldShowReceiptEmptyState) { - receiptStyle = styles.fullHeight; + receiptStyle = styles.h100; } else if (fillSpace) { receiptStyle = styles.flexibleHeight; } else { diff --git a/src/styles/index.ts b/src/styles/index.ts index 552bbfffd842..d3225c687cbe 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5194,10 +5194,6 @@ const staticStyles = (theme: ThemeColors) => width: Animated.add(variables.sideBarWidth, receiptPaneRHPWidth), }, - fullHeight: { - height: '100%', - }, - flexibleHeight: { height: 'auto', minHeight: 200, From 7c1f3e008807a1ec46db311d3e25bd429f86961a Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 30 Sep 2025 09:30:46 +0200 Subject: [PATCH 51/56] Add comment explaining dismissing wide RHP and navigating to ROUTES.SEARCH_MONEY_REQUEST_REPORT --- src/libs/actions/IOU.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3092e28f30c0..b41e0f9485ec 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -912,6 +912,7 @@ function dismissModalAndOpenReportInInboxTab(reportID?: string) { const rhpKey = rootState.routes.at(-1)?.state?.key; if (rhpKey) { const hasMultipleTransactions = Object.values(allTransactions).filter((transaction) => transaction?.reportID === reportID).length > 0; + // When a report with one expense is opened in the wide RHP and the user adds another expense, RHP should be dismissed and ROUTES.SEARCH_MONEY_REQUEST_REPORT should be displayed. if (hasMultipleTransactions && reportID) { Navigation.dismissModal(); InteractionManager.runAfterInteractions(() => { From 1a786909572b28532eaea48bf9349650c5494b37 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 1 Oct 2025 15:29:25 +0200 Subject: [PATCH 52/56] Don't display wide rhp for invoices --- src/components/Search/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index c00e8fc51d08..dac19ff8e157 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -657,7 +657,9 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS return; } - if (!isTask) { + const isInvoice = item?.report?.type === CONST.REPORT.TYPE.INVOICE; + + if (!isTask && !isInvoice) { markReportIDAsExpense(reportID); } From aa4cf2ab56d32c7fa76080392286a3a1e668ddab Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 1 Oct 2025 16:10:58 +0200 Subject: [PATCH 53/56] Fix displaying empty receipt state after deleting receipt --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 7e6bed278609..b04dd04d220c 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -154,7 +154,7 @@ function MoneyRequestReceiptView({ const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => (pendingAction ? undefined : transaction?.pendingFields?.[fieldPath]); const isReceiptAllowed = !isPaidReport && !isInvoice; - const shouldShowReceiptEmptyState = isReceiptAllowed && !hasReceipt && !!transaction && isLoading; + const shouldShowReceiptEmptyState = isReceiptAllowed && !hasReceipt && !!transaction && !transaction.receipt; const [receiptImageViolations, receiptViolations] = useMemo(() => { const imageViolations = []; const allViolations = []; From 263c289f20f3492255d062ce644703adf49d6f62 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 1 Oct 2025 16:24:45 +0200 Subject: [PATCH 54/56] Set marginBottom:20 for receipt empty state --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index b04dd04d220c..4e412d9de0a1 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -243,7 +243,7 @@ function MoneyRequestReceiptView({ {shouldShowReceiptEmptyState && ( Date: Thu, 2 Oct 2025 11:10:04 +0200 Subject: [PATCH 55/56] Fix empty receipt state size when error is displayed --- .../ReportActionItem/MoneyRequestReceiptView.tsx | 12 +++++++++--- src/pages/home/ReportScreen.tsx | 1 + src/styles/index.ts | 3 ++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 4e412d9de0a1..eff453c827a0 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -68,6 +68,9 @@ type MoneyRequestReceiptViewProps = { /** Whether the receipt view should fill the given space */ fillSpace?: boolean; + + /** Whether it's displayed in Wide RHP */ + isDisplayedInWideRHP?: boolean; }; const receiptImageViolationNames: OnyxTypes.ViolationName[] = [ @@ -89,6 +92,7 @@ function MoneyRequestReceiptView({ isFromReviewDuplicates = false, fillSpace = false, mergeTransactionID, + isDisplayedInWideRHP = false, }: MoneyRequestReceiptViewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -187,6 +191,8 @@ function MoneyRequestReceiptView({ ...parentReportAction?.errors, }; + const showReceiptErrorWithEmptyState = shouldShowReceiptEmptyState && !hasReceipt && !isEmptyObject(errors); + const [showConfirmDismissReceiptError, setShowConfirmDismissReceiptError] = useState(false); const dismissReceiptError = useCallback(() => { @@ -243,7 +249,7 @@ function MoneyRequestReceiptView({ {shouldShowReceiptEmptyState && ( {hasReceipt && ( @@ -308,7 +314,7 @@ function MoneyRequestReceiptView({ )} {!!shouldShowAuditMessage && hasReceipt && ( - + )} diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index f2843bcb7563..7de1b52ccfd3 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -902,6 +902,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { allReports={allReports} report={transactionThreadReport ?? report} fillSpace + isDisplayedInWideRHP /> diff --git a/src/styles/index.ts b/src/styles/index.ts index 19e6eaf634d4..da0529f6c36c 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5212,7 +5212,8 @@ const staticStyles = (theme: ThemeColors) => }, wideRHPMoneyRequestReceiptViewScrollViewContainer: { - paddingTop: 12, + ...spacing.pt3, + ...spacing.pb2, minHeight: '100%', }, From dfa84359befb9be88088fd7d05490ae6a3c62a58 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 3 Oct 2025 08:20:55 +0200 Subject: [PATCH 56/56] Hide audit message when loading bar is displayed --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index eff453c827a0..967e6ba4d1c9 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -313,7 +313,7 @@ function MoneyRequestReceiptView({ /> )} - {!!shouldShowAuditMessage && hasReceipt && ( + {!!shouldShowAuditMessage && hasReceipt && !isLoading && (