diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index c21deb495d39d..99242b833a416 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -177,6 +177,7 @@ function MoneyRequestReportActionsList({ const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${getNonEmptyStringOnyxID(reportID)}`, {canBeMissing: true}); + const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`, {canBeMissing: true}); const shouldShowHarvestCreatedAction = isHarvestCreatedExpenseReport(reportNameValuePairs?.origin, reportNameValuePairs?.originalID); const [offlineModalVisible, setOfflineModalVisible] = useState(false); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); @@ -326,6 +327,12 @@ function MoneyRequestReportActionsList({ if (!isFocused) { return; } + + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } + if (isUnread(report, transactionThreadReport, isReportArchived) || (lastAction && isCurrentActionUnread(report, lastAction, visibleReportActions))) { // On desktop, when the notification center is displayed, isVisible will return false. // Currently, there's no programmatic way to dismiss the notification center panel. @@ -341,7 +348,7 @@ function MoneyRequestReportActionsList({ } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [report.lastVisibleActionCreated, transactionThreadReport?.lastVisibleActionCreated, report.reportID, isVisible]); + }, [report.lastVisibleActionCreated, transactionThreadReport?.lastVisibleActionCreated, report.reportID, isVisible, reportMetadata?.hasOnceLoadedReportActions]); useEffect(() => { if (!isVisible || !isFocused) { @@ -351,6 +358,11 @@ function MoneyRequestReportActionsList({ return; } + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } + // In case the user read new messages (after being inactive) with other device we should // show marker based on report.lastReadTime const newMessageTimeReference = lastMessageTime.current && report.lastReadTime && lastMessageTime.current > report.lastReadTime ? userActiveSince.current : report.lastReadTime; @@ -373,7 +385,7 @@ function MoneyRequestReportActionsList({ // We will mark the report as read in the above case which marks the LHN report item as read while showing the new message // marker for the chat messages received while the user wasn't focused on the report or on another browser tab for web. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isFocused, isVisible]); + }, [isFocused, isVisible, reportMetadata?.hasOnceLoadedReportActions]); /** * The index of the earliest message that was received while offline @@ -443,6 +455,7 @@ function MoneyRequestReportActionsList({ // We additionally track the top offset to be able to scroll to the new transaction when it's added scrollingVerticalTopOffset.current = contentOffset.y; }, + hasOnceLoadedReportActions: !!reportMetadata?.hasOnceLoadedReportActions, }); useEffect(() => { @@ -629,8 +642,11 @@ function MoneyRequestReportActionsList({ reportScrollManager.scrollToEnd(); readActionSkipped.current = false; - readNewestAction(report.reportID); - }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID]); + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (reportMetadata?.hasOnceLoadedReportActions) { + readNewestAction(report.reportID); + } + }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, reportMetadata?.hasOnceLoadedReportActions]); const scrollToNewTransaction = useCallback( (pageY: number) => { diff --git a/src/pages/inbox/ReportScreen.tsx b/src/pages/inbox/ReportScreen.tsx index 6cac880586ec4..608f4ecfb24ef 100644 --- a/src/pages/inbox/ReportScreen.tsx +++ b/src/pages/inbox/ReportScreen.tsx @@ -940,9 +940,13 @@ function ReportScreen({route, navigation, isInSidePanel = false}: ReportScreenPr if (!!report?.lastReadTime || !isTaskReport(report)) { return; } + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } // After creating the task report then navigating to task detail we don't have any report actions and the last read time is empty so We need to update the initial last read time when opening the task report detail. readNewestAction(report?.reportID); - }, [report]); + }, [report, reportMetadata?.hasOnceLoadedReportActions]); // Reset the ref when navigating to a different report useEffect(() => { diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index d33222752e842..4ad364ff9a9d1 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -368,6 +368,7 @@ function ReportActionsList({ setShouldScrollToEndAfterLayout(false); } }, + hasOnceLoadedReportActions: !!reportMetadata?.hasOnceLoadedReportActions, }); useEffect(() => { @@ -430,6 +431,11 @@ function ReportActionsList({ return; } + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } + if (!isVisible || !isFocused) { if (!lastMessageTime.current) { lastMessageTime.current = lastAction?.created ?? ''; @@ -464,7 +470,7 @@ function ReportActionsList({ // We will mark the report as read in the above case which marks the LHN report item as read while showing the new message // marker for the chat messages received while the user wasn't focused on the report or on another browser tab for web. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isFocused, isVisible]); + }, [isFocused, isVisible, reportMetadata?.hasOnceLoadedReportActions]); const prevHandleReportChangeMarkAsRead = useRef<() => void>(null); const prevHandleAppVisibilityMarkAsRead = useRef<() => void>(null); @@ -631,8 +637,11 @@ function ReportActionsList({ } reportScrollManager.scrollToBottom(); readActionSkipped.current = false; - readNewestAction(report.reportID); - }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, backTo]); + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (reportMetadata?.hasOnceLoadedReportActions) { + readNewestAction(report.reportID); + } + }, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, backTo, reportMetadata?.hasOnceLoadedReportActions]); /** * Calculates the ideal number of report actions to render in the first render, based on the screen height and on diff --git a/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts b/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts index 12016b2b56d54..6c84e47c084a9 100644 --- a/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts +++ b/src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts @@ -23,6 +23,9 @@ type Args = { /** Callback to call on every scroll event */ onTrackScrolling: (event: NativeSyntheticEvent) => void; + + /** Whether the report actions have been loaded at least once */ + hasOnceLoadedReportActions: boolean; }; export default function useReportUnreadMessageScrollTracking({ @@ -32,14 +35,16 @@ export default function useReportUnreadMessageScrollTracking({ onTrackScrolling, unreadMarkerReportActionIndex, isInverted, + hasOnceLoadedReportActions, }: Args) { const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(false); const isFocused = useIsFocused(); - const ref = useRef<{previousViewableItems: ViewToken[]; reportID: string; unreadMarkerReportActionIndex: number; isFocused: boolean}>({ + const ref = useRef<{previousViewableItems: ViewToken[]; reportID: string; unreadMarkerReportActionIndex: number; isFocused: boolean; hasOnceLoadedReportActions: boolean}>({ reportID, unreadMarkerReportActionIndex, previousViewableItems: [], isFocused: true, + hasOnceLoadedReportActions: false, }); // We want to save the updated value on ref to use it in onViewableItemsChanged // because FlatList requires the callback to be stable and we cannot add a dependency on the useCallback. @@ -52,6 +57,10 @@ export default function useReportUnreadMessageScrollTracking({ ref.current.isFocused = isFocused; }, [isFocused]); + useEffect(() => { + ref.current.hasOnceLoadedReportActions = hasOnceLoadedReportActions; + }, [hasOnceLoadedReportActions]); + /** * On every scroll event we want to: * Show/hide the latest message pill when user is scrolling back/forth in the history of messages. @@ -110,7 +119,8 @@ export default function useReportUnreadMessageScrollTracking({ } // if we're scrolled closer than the offset and read action has been skipped then mark message as read - if (unreadActionVisible && readActionSkippedRef.current) { + // Do not try to mark the report as read if the report has not been loaded and shared with the user + if (unreadActionVisible && readActionSkippedRef.current && ref.current.hasOnceLoadedReportActions) { // eslint-disable-next-line no-param-reassign readActionSkippedRef.current = false; readNewestAction(ref.current.reportID); diff --git a/tests/unit/useReportUnreadMessageScrollTrackingTest.ts b/tests/unit/useReportUnreadMessageScrollTrackingTest.ts index 68ce4f0609aed..beda62ec194c5 100644 --- a/tests/unit/useReportUnreadMessageScrollTrackingTest.ts +++ b/tests/unit/useReportUnreadMessageScrollTrackingTest.ts @@ -40,6 +40,7 @@ describe('useReportUnreadMessageScrollTracking', () => { unreadMarkerReportActionIndex: -1, isInverted: true, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -69,6 +70,7 @@ describe('useReportUnreadMessageScrollTracking', () => { isInverted: true, unreadMarkerReportActionIndex: -1, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -95,6 +97,7 @@ describe('useReportUnreadMessageScrollTracking', () => { isInverted: true, unreadMarkerReportActionIndex: 1, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -126,6 +129,7 @@ describe('useReportUnreadMessageScrollTracking', () => { unreadMarkerReportActionIndex: -1, isInverted: true, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -151,6 +155,7 @@ describe('useReportUnreadMessageScrollTracking', () => { unreadMarkerReportActionIndex: 1, isInverted: true, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), ); @@ -180,6 +185,7 @@ describe('useReportUnreadMessageScrollTracking', () => { unreadMarkerReportActionIndex: 1, isInverted: true, onTrackScrolling: onTrackScrollingMockFn, + hasOnceLoadedReportActions: true, }), );