Skip to content
Closed
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
4 changes: 1 addition & 3 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ const decorators = [
SearchContextProvider,
]}
>
<ScreenWrapperStatusContext.Provider
value={{didScreenTransitionEnd: true, shouldUseNarrowLayoutOnWideRHP: false, isSafeAreaTopPaddingApplied: false, isSafeAreaBottomPaddingApplied: false}}
>
<ScreenWrapperStatusContext.Provider value={{didScreenTransitionEnd: true, isSafeAreaTopPaddingApplied: false, isSafeAreaBottomPaddingApplied: false}}>
<Story />
</ScreenWrapperStatusContext.Provider>
</ComposeProviders>
Expand Down
1 change: 0 additions & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@ const CONST = {
BACKGROUND_IMAGE_TRANSITION_DURATION: 1000,
SCREEN_TRANSITION_END_TIMEOUT: 1000,
PENDING_TRANSACTION_DELETION_DELAY: 4000,
PENDING_TRANSACTION_FRESHNESS_WINDOW: 60000,
PENDING_TRANSACTION_SCROLL_DELAY: 1000,

// Delay before pre-inserting the Search fullscreen route under the RHP on the confirmation screen.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import useAppFocusEvent from '@hooks/useAppFocusEvent';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import {useIsReportLoadPending} from '@hooks/useInFlightRequests';
import useIsReportActionsLoaded from '@hooks/useIsReportActionsLoaded';
import useIsReportVisible from '@hooks/useIsReportVisible';
import useLoadReportActions from '@hooks/useLoadReportActions';
import useLocalize from '@hooks/useLocalize';
import useNetworkWithOfflineStatus from '@hooks/useNetworkWithOfflineStatus';
Expand Down Expand Up @@ -121,7 +120,8 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
const [isVisible, setIsVisible] = useState(Visibility.isVisible);
const isFocused = useIsFocused();
const {shouldUseNarrowLayout} = useResponsiveLayoutOnWideRHP();
const isReportVisible = useIsReportVisible(shouldUseNarrowLayout);
// The table is visible whenever it's wide, or — on narrow — only when focused (the RHP has closed).
const isReportVisible = shouldUseNarrowLayout ? isFocused : true;
const route = useRoute<PlatformStackRouteProp<ReportsSplitNavigatorParamList, typeof SCREENS.REPORT>>();
const reportIDFromRoute = route?.params?.reportID;
const isReportLoadPending = useIsReportLoadPending(reportIDFromRoute);
Expand Down Expand Up @@ -154,7 +154,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
const [pendingNewTransactionIDs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {
selector: pendingNewTransactionIDsSelector,
});
const newTransactions = useNewTransactions(reportLoadingState?.hasOnceLoadedReportActions, reportTransactions, pendingNewTransactionIDs, reportIDFromRoute, isReportVisible);
const newTransactions = useNewTransactions(reportLoadingState?.hasOnceLoadedReportActions, reportTransactions, pendingNewTransactionIDs, reportIDFromRoute, isFocused);
const showReportActionsLoadingState = reportLoadingState?.isLoadingInitialReportActions && !reportLoadingState?.hasOnceLoadedReportActions;
const isInitialReportLoadPending = !isOffline && isReportLoadPending && !reportLoadingState?.hasOnceLoadedReportActions;
const reportTransactionIDs = useMemo(() => transactions.map((transaction) => transaction.transactionID), [transactions]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import TransactionPreview from '@components/ReportActionItem/TransactionPreview'
import {useWideRHPActions} from '@components/WideRHPContextProvider';

import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useIsReportVisible from '@hooks/useIsReportVisible';
import useNetwork from '@hooks/useNetwork';
import useNewTransactions from '@hooks/useNewTransactions';
import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
import useReportTransactionsCollection from '@hooks/useReportTransactionsCollection';
import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
Expand Down Expand Up @@ -77,10 +76,9 @@ function MoneyRequestReportPreview({
}: MoneyRequestReportPreviewProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {shouldUseNarrowLayoutIgnoringWideRHP, isSmallScreenWidth} = useResponsiveLayoutOnWideRHP();
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
const {markReportRHPWidth, unmarkReportRHPWidth} = useWideRHPActions();
// Deferred presses need focus, which is narrower than the visibility the highlight below is gated on.
const isFocused = useIsFocused();
const personalDetailsList = usePersonalDetails();
const {email: currentUserEmail, accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
Expand Down Expand Up @@ -162,8 +160,8 @@ function MoneyRequestReportPreview({
);

const reportPreviewStyles = useMemo(
() => StyleUtils.getMoneyRequestReportPreviewStyle(shouldUseNarrowLayoutIgnoringWideRHP, transactions.length, widths.currentWidth, widths.currentWrapperWidth),
[StyleUtils, widths, shouldUseNarrowLayoutIgnoringWideRHP, transactions.length],
() => StyleUtils.getMoneyRequestReportPreviewStyle(shouldUseNarrowLayout, transactions.length, widths.currentWidth, widths.currentWrapperWidth),
[StyleUtils, widths, shouldUseNarrowLayout, transactions.length],
);
const shouldShowPayerAndReceiver = useMemo(() => {
if (!isIOUReport(iouReport) && action.childType !== CONST.REPORT.TYPE.IOU) {
Expand Down Expand Up @@ -216,6 +214,7 @@ function MoneyRequestReportPreview({
const [pendingNewTransactionIDs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${chatReportID}`, {
selector: pendingNewTransactionIDsSelector,
});
const isFocused = useIsFocused();
// Transactions arrive in batches and `useNewTransactions` would diff each batch as newly added expenses.
// Withhold the list until every transaction the report claims has arrived.
const expectedTransactionCount = iouReport?.transactionCount ?? 0;
Expand All @@ -228,10 +227,9 @@ function MoneyRequestReportPreview({
setHasCompletedDelivery(true);
}
const transactionsForDiff = isDeliveryComplete || hasCompletedDelivery ? transactions : undefined;
const newTransactions = useNewTransactions(hasOnceLoadedReportActions, transactionsForDiff, pendingNewTransactionIDs, chatReportID, isFocused);
// Don't surface the highlight while the preview is covered — it'd animate the one-shot off-screen and be missed.
// A modal pane can be covered at any width, so this reads the flag unadjusted for wide RHP.
const isReportVisible = useIsReportVisible(shouldUseNarrowLayoutIgnoringWideRHP);
const newTransactions = useNewTransactions(hasOnceLoadedReportActions, transactionsForDiff, pendingNewTransactionIDs, chatReportID, isReportVisible);
const isReportVisible = shouldUseNarrowLayout ? isFocused : true;
const newTransactionIDs = new Set(isReportVisible ? newTransactions.map((transaction) => transaction.transactionID) : []);

const transactionPreviewContainerStyles = [styles.h100, reportPreviewStyles.transactionPreviewCarouselStyle];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {createContext} from 'react';

type ScreenWrapperStatusContextType = {
didScreenTransitionEnd: boolean;
shouldUseNarrowLayoutOnWideRHP: boolean;
isSafeAreaTopPaddingApplied: boolean;
isSafeAreaBottomPaddingApplied: boolean;
};
Expand Down
11 changes: 6 additions & 5 deletions src/components/ScreenWrapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import useAccessibilityFocus from '@hooks/useAccessibilityFocus';
import useEnvironment from '@hooks/useEnvironment';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSafeAreaPaddings from '@hooks/useSafeAreaPaddings';
import useThemeStyles from '@hooks/useThemeStyles';

Expand Down Expand Up @@ -124,7 +124,8 @@ function ScreenWrapper({
const mergedScreenWrapperRef = mergeRefs(screenWrapperRef, ref);

// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout for a case where we want to show the offline indicator only on small screens
const {isSmallScreenWidth, shouldUseNarrowLayoutIgnoringWideRHP, shouldUseNarrowLayout: shouldUseNarrowLayoutOnWideRHP} = useResponsiveLayoutOnWideRHP();
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();

const styles = useThemeStyles();
const {isDevelopment} = useEnvironment();
Expand All @@ -143,14 +144,14 @@ function ScreenWrapper({
const includeSafeAreaPaddingBottom = isUsingEdgeToEdgeMode ? false : includeSafeAreaPaddingBottomProp;
const isSafeAreaTopPaddingApplied = includePaddingTop;
const statusContextValue = useMemo(
() => ({didScreenTransitionEnd, shouldUseNarrowLayoutOnWideRHP, isSafeAreaTopPaddingApplied, isSafeAreaBottomPaddingApplied: includeSafeAreaPaddingBottom}),
[didScreenTransitionEnd, shouldUseNarrowLayoutOnWideRHP, includeSafeAreaPaddingBottom, isSafeAreaTopPaddingApplied],
() => ({didScreenTransitionEnd, isSafeAreaTopPaddingApplied, isSafeAreaBottomPaddingApplied: includeSafeAreaPaddingBottom}),
[didScreenTransitionEnd, includeSafeAreaPaddingBottom, isSafeAreaTopPaddingApplied],
);

// This context allows us to disable the safe area padding offsetting the offline indicator in scrollable components like 'ScrollView', 'SelectionList' or 'FormProvider'.
// This is useful e.g. for the RightModalNavigator, where we want to avoid the safe area padding offsetting the offline indicator because we only show the offline indicator on small screens.
const {isInNarrowPane} = useContext(NarrowPaneContext);
const isMobileWebNarrowLayout = getPlatform() === CONST.PLATFORM.WEB && isMobile() && shouldUseNarrowLayoutIgnoringWideRHP;
const isMobileWebNarrowLayout = getPlatform() === CONST.PLATFORM.WEB && isMobile() && shouldUseNarrowLayout;
const shouldMoveAccessibilityFocus = isMobileWebNarrowLayout && isInNarrowPane;
const shouldHideFromAccessibility = isMobileWebNarrowLayout && !isFocused;
const {addSafeAreaPadding, showOnSmallScreens, showOnWideScreens} = useContext(ScreenWrapperOfflineIndicatorContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,11 @@ function TransactionGroupListExpandedImpl({
});
return;
}
markReportRHPWidth(reportID, 'wide');
const route = ROUTES.SEARCH_REPORT.getRoute({reportID, backTo});
// Marked after the new-tab guard: opening in a new tab never mounts the screen that would consume the hint, so it would pin this report wide on a later visit.
if (openInternalRouteInNewTab(route, event)) {
return;
}
markReportRHPWidth(reportID, 'wide');
Navigation.navigate(route);
};

Expand Down
17 changes: 8 additions & 9 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,12 @@ function Search({
}
}

if (item.transactions.length > 1) {
markReportRHPWidth(reportID, 'super-wide');
} else {
unmarkReportRHPWidth(reportID, 'super-wide');
}

// Persist the current search context so prev/next navigation arrows
// in the report RHP can reference the correct result set.
saveLastSearchParams({
Expand All @@ -687,17 +693,9 @@ function Search({
reportID,
backTo,
});
if (item.transactions.length <= 1) {
// A report that no longer qualifies must not keep the hint an earlier visit left behind, whether or not this click navigates.
unmarkReportRHPWidth(reportID, 'super-wide');
}
// Marked after the new-tab guard: opening in a new tab never mounts the screen that would consume the hint, so it would pin this report wide on a later visit.
if (openInternalRouteInNewTab(route, event)) {
return;
}
if (item.transactions.length > 1) {
markReportRHPWidth(reportID, 'super-wide');
}
requestAnimationFrame(() => Navigation.navigate(route));
return;
}
Expand Down Expand Up @@ -730,6 +728,8 @@ function Search({
return;
}

markReportRHPWidth(reportID, 'wide');

if (isTransactionItem && transactionPreviewData) {
setOptimisticDataForTransactionThreadPreview(transactionItem, transactionPreviewData, getCurrencyDecimals, transactionItem?.reportAction?.childReportID);
}
Expand All @@ -738,7 +738,6 @@ function Search({
if (openInternalRouteInNewTab(route, event)) {
return;
}
markReportRHPWidth(reportID, 'wide');
requestAnimationFrame(() => Navigation.navigate(route));
},
[
Expand Down
4 changes: 4 additions & 0 deletions src/components/WideRHPContextProvider/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const defaultWideRHPActionsContextValue: WideRHPActionsContextType = {
markReportRHPWidth: () => {},
unmarkReportRHPWidth: () => {},
getReportRHPWidthHint: () => undefined,
syncRHPKeys: () => {},
clearWideRHPKeys: () => {},
setIsWideRHPClosing: () => {},
setIsSuperWideRHPClosing: () => {},
};

export {defaultWideRHPStateContextValue, defaultWideRHPActionsContextValue};
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import getVisibleRHPKeys from './getVisibleRHPRouteKeys';

// Helper function to determine if wide or super wide RHP is displayed below the currently focused route
export default function getIsRHPDisplayedBelow(focusedRouteKey: string | undefined, visibleSuperWideRHPRouteKeys: string[], visibleWideRHPRouteKeys: string[]) {
export default function getIsRHPDisplayedBelow(focusedRouteKey: string | undefined, allSuperWideRHPRouteKeys: string[], allWideRHPRouteKeys: string[]) {
const {visibleSuperWideRHPRouteKeys, visibleWideRHPRouteKeys} = getVisibleRHPKeys(allSuperWideRHPRouteKeys, allWideRHPRouteKeys);

if (!focusedRouteKey) {
return {
isWideRHPBelow: false,
Expand Down
59 changes: 29 additions & 30 deletions src/components/WideRHPContextProvider/getVisibleRHPRouteKeys.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,54 @@
import extractNavigationKeys from '@libs/Navigation/helpers/extractNavigationKeys';
import getLastVisibleRHPRouteKey from '@libs/Navigation/helpers/getLastVisibleRHPRouteKey';

import type {NavigationState} from '@react-navigation/native';
import {navigationRef} from '@libs/Navigation/Navigation';

type VisibleRHPKeys = {
visibleWideRHPRouteKeys: string[];
visibleSuperWideRHPRouteKeys: string[];
presentRouteKeys: string[];
};

const emptyRHPKeysState: VisibleRHPKeys = {
visibleWideRHPRouteKeys: [],
visibleSuperWideRHPRouteKeys: [],
presentRouteKeys: [],
};

/**
* A key missing from the navigation state may be a screen dismissing or one that was never shown, and only the first
* holds a width. Callers must record `presentRouteKeys` so `seenRouteKeys` can tell those apart.
* Extracts the keys of the screens that are currently displayed from the array of all Wide/Super Wide RHP keys
*
* @param allWideRHPKeys - an array of all Wide/Super Wide RHP keys
*/
function getVisibleRHPKeys(state: NavigationState | undefined, allWideRHPKeys: string[], allSuperWideRHPKeys: string[], seenRouteKeys: ReadonlySet<string>): VisibleRHPKeys {
// Nothing registered is the common case, and there is then no key to match the tree against.
if (!state || (!allWideRHPKeys.length && !allSuperWideRHPKeys.length)) {
function getVisibleRHPKeys(allSuperWideRHPKeys: string[], allWideRHPKeys: string[]): VisibleRHPKeys {
if (!navigationRef.isReady()) {
return emptyRHPKeysState;
}

const rootState = navigationRef.getRootState();
if (!rootState) {
return emptyRHPKeysState;
}

// A covered RHP needs no hand-clearing: this returns undefined for it, and its keys are still in the state below, so the displayed check rejects them too.
const lastVisibleRHPRouteKey = getLastVisibleRHPRouteKey(state);
const lastRHPRoute = state.routes.find((route) => route.key === lastVisibleRHPRouteKey);

let visibleRHPKeys = new Set<string>();
if (lastRHPRoute?.state?.routes) {
const superWideRHPIndex = lastRHPRoute.state.routes.findLastIndex((route) => route?.key && allSuperWideRHPKeys.includes(route.key));
const wideRHPIndex = lastRHPRoute.state.routes.findLastIndex((route) => route?.key && allWideRHPKeys.includes(route.key));

if (superWideRHPIndex > -1) {
visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state.routes.slice(superWideRHPIndex));
} else if (wideRHPIndex > -1) {
visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state.routes.slice(wideRHPIndex));
} else {
visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state.routes);
}
const lastVisibleRHPRouteKey = getLastVisibleRHPRouteKey(rootState);
const lastRHPRoute = rootState.routes.find((route) => route.key === lastVisibleRHPRouteKey);

if (!lastRHPRoute) {
return emptyRHPKeysState;
}

const keysInState = extractNavigationKeys(state.routes);
const isDisplayed = (key: string) => visibleRHPKeys.has(key) || (seenRouteKeys.has(key) && !keysInState.has(key));
const superWideRHPIndex = lastRHPRoute.state?.routes.findLastIndex((route) => route?.key && allSuperWideRHPKeys.includes(route.key)) ?? -1;
const wideRHPIndex = lastRHPRoute.state?.routes.findLastIndex((route) => route?.key && allWideRHPKeys.includes(route.key)) ?? -1;

let visibleRHPKeys;
if (superWideRHPIndex > -1) {
visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state?.routes.slice(superWideRHPIndex));
} else if (wideRHPIndex > -1) {
visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state?.routes.slice(wideRHPIndex));
} else {
visibleRHPKeys = extractNavigationKeys(lastRHPRoute.state?.routes);
}

return {
visibleWideRHPRouteKeys: allWideRHPKeys.filter(isDisplayed),
visibleSuperWideRHPRouteKeys: allSuperWideRHPKeys.filter(isDisplayed),
presentRouteKeys: [...allWideRHPKeys, ...allSuperWideRHPKeys].filter((key) => keysInState.has(key)),
visibleWideRHPRouteKeys: allWideRHPKeys.filter((key) => visibleRHPKeys.has(key)),
visibleSuperWideRHPRouteKeys: allSuperWideRHPKeys.filter((key) => visibleRHPKeys.has(key)),
};
}

Expand Down
Loading
Loading