Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,10 @@ const ONYXKEYS = {
/** List of transaction IDs used when navigating to prev/next transaction when viewing it in RHP */
TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS: 'transactionThreadNavigationTransactionIDs',

/** Hash of the search snapshot that holds the transactions referenced by TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS.
* Used to fall back to snapshot data when the live transaction collection hasn't loaded those transactions yet (e.g. opening an expense from the Spend page as an approver). */
TRANSACTION_THREAD_NAVIGATION_SNAPSHOT_HASH: 'transactionThreadNavigationSnapshotHash',

/** Optional map of transactionID -> sibling descriptor for prev/next navigation in snapshot-backed flows (e.g. Home "Recently added"), where siblings may be absent from the main Onyx collections. When set, navigation resolves (and lazily creates) each sibling's thread on demand from its descriptor. */
TRANSACTION_THREAD_NAVIGATION_THREAD_REPORT_IDS: 'transactionThreadNavigationThreadReportIDs',

Expand Down Expand Up @@ -1747,6 +1751,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.REPORT_NAVIGATION_LAST_SEARCH_QUERY]: OnyxTypes.LastSearchParams;
[ONYXKEYS.NVP_LAST_ANDROID_LOGIN]: string;
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS]: string[];
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_SNAPSHOT_HASH]: number;
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_THREAD_REPORT_IDS]: Record<string, TransactionThreadNavigationDescriptor>;
[ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES]: OnyxTypes.ExportTemplate[];
[ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION]: OnboardingAccounting;
Expand Down
40 changes: 30 additions & 10 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import MoneyReportHeaderActions from './MoneyReportHeaderActions';
import {ExportDownloadStatusProvider} from './MoneyReportHeaderActions/ExportDownloadStatusContext';
import MoneyReportHeaderModals from './MoneyReportHeaderModals';
import MoneyReportHeaderMoreContent from './MoneyReportHeaderMoreContent';
import MoneyRequestReportNavigation from './MoneyRequestReportView/MoneyRequestReportNavigation';
import MoneyRequestReportTransactionsNavigation from './MoneyRequestReportView/MoneyRequestReportTransactionsNavigation';
import {PaymentAnimationsProvider} from './PaymentAnimationsContext';
import {useSearchSelectionActions} from './Search/SearchContext';

Expand Down Expand Up @@ -79,14 +81,23 @@ function MoneyReportHeaderContent({reportID: reportIDProp, shouldDisplayBackButt

const transactions = Object.values(reportTransactions);

const [activeTransactionIDs] = useOnyx(ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS);

const singleTransactionID = transactions.length === 1 ? transactions.at(0)?.transactionID : undefined;

const anchorTransactionIDFromRoute = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT ? route.params.anchorTransactionID : undefined;
const multiTxAnchorTransactionID = anchorTransactionIDFromRoute && activeTransactionIDs?.includes(anchorTransactionIDFromRoute) ? anchorTransactionIDFromRoute : undefined;
const carouselAnchorTransactionID = singleTransactionID ?? multiTxAnchorTransactionID;
const shouldShowTransactionNavigation = !!carouselAnchorTransactionID && !!activeTransactionIDs?.includes(carouselAnchorTransactionID);

const styles = useThemeStyles();

const {isWideRHPDisplayedOnWideLayout, isSuperWideRHPDisplayedOnWideLayout} = useResponsiveLayoutOnWideRHP();

const shouldShowHeaderButtonsInHeaderRow = isInLandscapeMode || !shouldDisplayNarrowVersion || isWideRHPDisplayedOnWideLayout || isSuperWideRHPDisplayedOnWideLayout;
const isReportInRHP = route.name !== SCREENS.REPORT;
const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth;
const isReportInSearch = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT || route.name === SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT;
const shouldDisplaySearchRouter = !isReportInRHP || (isSmallScreenWidth && !isReportInSearch);

const backTo = (route.params as {backTo?: Route} | undefined)?.backTo;

Expand Down Expand Up @@ -135,14 +146,18 @@ function MoneyReportHeaderContent({reportID: reportIDProp, shouldDisplayBackButt
shouldEnableDetailPageNavigation
openParentReportInCurrentTab
>
{shouldShowHeaderButtonsInHeaderRow && (
<MoneyReportHeaderActions
reportID={reportIDProp}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
/>
)}
{isReportInSearch &&
(shouldShowTransactionNavigation && carouselAnchorTransactionID ? (
<MoneyRequestReportTransactionsNavigation
currentTransactionID={carouselAnchorTransactionID}
shouldDisplayNarrowVersion={!shouldShowHeaderButtonsInHeaderRow}
/>
) : (
<MoneyRequestReportNavigation
reportID={reportIDProp}
shouldDisplayNarrowVersion={!shouldShowHeaderButtonsInHeaderRow}
/>
))}
</HeaderWithBackButton>
{!shouldShowHeaderButtonsInHeaderRow && (
<MoneyReportHeaderActions
Expand All @@ -152,7 +167,12 @@ function MoneyReportHeaderContent({reportID: reportIDProp, shouldDisplayBackButt
backTo={backTo}
/>
)}
<MoneyReportHeaderMoreContent reportID={reportIDProp} />
<MoneyReportHeaderMoreContent
reportID={reportIDProp}
primaryAction={primaryAction}
backTo={backTo}
shouldShowHeaderButtonsInHeaderRow={shouldShowHeaderButtonsInHeaderRow}
/>
<HeaderLoadingBar />
</View>
);
Expand Down
46 changes: 32 additions & 14 deletions src/components/MoneyReportHeaderMoreContent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import useMoneyReportHeaderStatusBar from '@hooks/useMoneyReportHeaderStatusBar';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP';
import useThemeStyles from '@hooks/useThemeStyles';

import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
Expand All @@ -12,6 +10,7 @@ import {isInvoiceReport as isInvoiceReportUtil} from '@libs/ReportUtils';

import type CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';

Expand All @@ -22,20 +21,25 @@ import {useRoute} from '@react-navigation/native';
import React from 'react';
import {View} from 'react-native';

import type {MoneyReportHeaderActionsProps} from './MoneyReportHeaderActions/types';

import MoneyReportHeaderActions from './MoneyReportHeaderActions';
import MoneyReportHeaderNextStep from './MoneyReportHeaderNextStep';
import MoneyReportHeaderStatusBarSection from './MoneyReportHeaderStatusBarSection';
import {useMoneyReportTransactionThread} from './MoneyReportTransactionThreadContext';
import MoneyRequestReportNavigation from './MoneyRequestReportView/MoneyRequestReportNavigation';

type MoneyReportHeaderMoreContentProps = {
reportID: string | undefined;
primaryAction: MoneyReportHeaderActionsProps['primaryAction'];
backTo: Route | undefined;
shouldShowHeaderButtonsInHeaderRow: boolean;
};

/**
* Cheap visibility gate — fetches minimal data to decide whether the more-content section
* should render at all, avoiding expensive hooks in the body when nothing is shown.
*/
function MoneyReportHeaderMoreContent({reportID}: MoneyReportHeaderMoreContentProps) {
function MoneyReportHeaderMoreContent({reportID, primaryAction, backTo, shouldShowHeaderButtonsInHeaderRow}: MoneyReportHeaderMoreContentProps) {
const route = useRoute<
| PlatformStackRouteProp<ReportsSplitNavigatorParamList, typeof SCREENS.REPORT>
| PlatformStackRouteProp<RightModalNavigatorParamList, typeof SCREENS.RIGHT_MODAL.EXPENSE_REPORT>
Expand All @@ -50,8 +54,10 @@ function MoneyReportHeaderMoreContent({reportID}: MoneyReportHeaderMoreContentPr

const isInvoiceReport = isInvoiceReportUtil(moneyRequestReport);
const shouldShowNextStep = isGroupPolicy(policy) && !isInvoiceReport && !shouldShowStatusBar;
const hasStatusOrNextStep = shouldShowNextStep || !!statusBarType;
const shouldRenderActionsInRow = shouldShowHeaderButtonsInHeaderRow;

const shouldShowMoreContent = shouldShowNextStep || !!statusBarType || isReportInSearch;
const shouldShowMoreContent = hasStatusOrNextStep || shouldRenderActionsInRow;

if (!shouldShowMoreContent) {
return null;
Expand All @@ -63,6 +69,9 @@ function MoneyReportHeaderMoreContent({reportID}: MoneyReportHeaderMoreContentPr
statusBarType={statusBarType}
isReportInSearch={isReportInSearch}
shouldShowNextStep={shouldShowNextStep}
primaryAction={primaryAction}
backTo={backTo}
shouldRenderActionsInRow={shouldRenderActionsInRow}
/>
);
}
Expand All @@ -72,20 +81,27 @@ type MoneyReportHeaderMoreContentBodyProps = {
statusBarType: ValueOf<typeof CONST.REPORT.STATUS_BAR_TYPE> | undefined;
isReportInSearch: boolean;
shouldShowNextStep: boolean;
primaryAction: MoneyReportHeaderActionsProps['primaryAction'];
backTo: Route | undefined;
shouldRenderActionsInRow: boolean;
};

function MoneyReportHeaderMoreContentBody({moneyRequestReport, statusBarType, isReportInSearch, shouldShowNextStep}: MoneyReportHeaderMoreContentBodyProps) {
function MoneyReportHeaderMoreContentBody({
moneyRequestReport,
statusBarType,
isReportInSearch,
shouldShowNextStep,
primaryAction,
backTo,
shouldRenderActionsInRow,
}: MoneyReportHeaderMoreContentBodyProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
const shouldDisplayNarrowVersion = shouldUseNarrowLayout || isMediumScreenWidth;
const {isWideRHPDisplayedOnWideLayout, isSuperWideRHPDisplayedOnWideLayout} = useResponsiveLayoutOnWideRHP();
const shouldDisplayNarrowMoreButton = !shouldDisplayNarrowVersion || isWideRHPDisplayedOnWideLayout || isSuperWideRHPDisplayedOnWideLayout;

const reportID = moneyRequestReport?.reportID;
const {iouTransactionID} = useMoneyReportTransactionThread();

return (
<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3]}>
<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3, shouldShowNextStep && styles.pt0]}>
<View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
{shouldShowNextStep && <MoneyReportHeaderNextStep reportID={reportID} />}
<MoneyReportHeaderStatusBarSection
Expand All @@ -94,10 +110,12 @@ function MoneyReportHeaderMoreContentBody({moneyRequestReport, statusBarType, is
iouTransactionID={iouTransactionID}
/>
</View>
{isReportInSearch && (
<MoneyRequestReportNavigation
{shouldRenderActionsInRow && (
<MoneyReportHeaderActions
reportID={reportID}
shouldDisplayNarrowVersion={!shouldDisplayNarrowMoreButton}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
/>
)}
</View>
Expand Down
22 changes: 16 additions & 6 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
const shouldDisplayTransactionNavigation = !!(reportID && isReportInRHP);
const shouldOpenParentReportInCurrentTab = !isSelfDM(parentReport);
const shouldDisplayButtonsInSeparateLine = useShouldDisplayButtonsInSeparateLine() && (wideRHPRouteKeys.length === 0 || isSmallScreenWidth);
const shouldDisplayNarrowVersion = shouldDisplayButtonsInSeparateLine;

const getStatusIcon: (src: IconAsset) => ReactNode = (src) => (
<Icon
Expand Down Expand Up @@ -179,7 +180,7 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
shouldEnableDetailPageNavigation
openParentReportInCurrentTab={shouldOpenParentReportInCurrentTab}
>
{!shouldDisplayButtonsInSeparateLine && (
{!shouldDisplayButtonsInSeparateLine && !statusBarProps && (
<MoneyRequestHeaderActions
reportID={reportID}
onBackButtonPress={onBackButtonPress}
Expand All @@ -189,6 +190,7 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
<MoneyRequestReportTransactionsNavigation
currentTransactionID={transaction.transactionID}
isFromReviewDuplicates={isFromReviewDuplicates}
shouldDisplayNarrowVersion={shouldDisplayNarrowVersion}
/>
)}
</HeaderWithBackButton>
Expand All @@ -199,11 +201,19 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
/>
)}
{!!statusBarProps && (
<View style={[styles.ph5, styles.pb3]}>
<MoneyRequestHeaderStatusBar
icon={statusBarProps.icon}
description={statusBarProps.description}
/>
<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3]}>
<View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
<MoneyRequestHeaderStatusBar
icon={statusBarProps.icon}
description={statusBarProps.description}
/>
</View>
{!shouldDisplayButtonsInSeparateLine && (
<MoneyRequestHeaderActions
reportID={reportID}
onBackButtonPress={onBackButtonPress}
/>
)}
</View>
)}
<HeaderLoadingBar />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {useSearchResultsContext} from '@components/Search/SearchContext';
import Text from '@components/Text';

import useFilterPendingDeleteReports from '@hooks/useFilterPendingDeleteReports';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useSearchSections from '@hooks/useSearchSections';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -21,6 +22,7 @@ import type LastSearchParams from '@src/types/onyx/ReportNavigation';

import type {OnyxEntry} from 'react-native-onyx';

import {useIsFocused} from '@react-navigation/native';
import React, {startTransition, useEffect, useState} from 'react';
import {View} from 'react-native';

Expand Down Expand Up @@ -113,6 +115,8 @@ function MoneyRequestReportNavigationStandalone({onReportsChange}: MoneyRequestR

function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersion, contextReports}: MoneyRequestReportNavigationContentProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const isFocused = useIsFocused();

// Lightweight subscriptions only: the current search query and its loading flag. These never mount
// the heavy useSearchSections subscription set, so the fast context path stays cheap.
Expand Down Expand Up @@ -151,7 +155,7 @@ function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersi
const shouldDisplayNavigationArrows = effectiveAllReports.length > 1 && currentIndex !== -1 && !!lastSearchQuery?.queryJSON;

useEffect(() => {
if (!lastSearchQuery?.queryJSON) {
if (!isFocused || !lastSearchQuery?.queryJSON) {
return;
}

Expand Down Expand Up @@ -181,7 +185,7 @@ function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersi
...lastSearchQuery,
previousLengthOfResults: effectiveAllReports.length,
});
}, [currentIndex, allReportsCount, effectiveAllReports.length, lastSearchQuery?.queryJSON, lastSearchQuery]);
}, [isFocused, currentIndex, allReportsCount, effectiveAllReports.length, lastSearchQuery?.queryJSON, lastSearchQuery]);

const goToReportId = (reportId?: string) => {
if (!reportId) {
Expand Down Expand Up @@ -242,7 +246,11 @@ function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersi
{!shouldUseContextReports && <MoneyRequestReportNavigationStandalone onReportsChange={setStandaloneReports} />}
{shouldDisplayNavigationArrows && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]}>
{!shouldDisplayNarrowVersion && <Text style={styles.mutedTextLabel}>{`${currentIndex + 1} of ${allReportsCount}`}</Text>}
{!shouldDisplayNarrowVersion && (
<Text style={[styles.mutedTextLabel, styles.textAlignRight, styles.mnw8]}>
{translate('common.currentOfTotal', {current: currentIndex + 1, total: allReportsCount})}
</Text>
)}
<PrevNextButtons
isPrevButtonDisabled={hidePrevButton}
isNextButtonDisabled={hideNextButton}
Expand Down
Loading
Loading