diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 149e27f62215a..3c5bdb4b1be73 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -8614,8 +8614,6 @@ const CONST = { ROTATE_BUTTON: 'Header-RotateButton', CLOSE_BUTTON: 'Header-CloseButton', MORE_BUTTON: 'Header-MoreButton', - PREVIOUS_BUTTON: 'Header-PreviousButton', - NEXT_BUTTON: 'Header-NextButton', }, TOP_BAR: { CANCEL_BUTTON: 'TopBar-CancelButton', diff --git a/src/components/FlatList/FlatListWithScrollKey/types.ts b/src/components/FlatList/FlatListWithScrollKey/types.ts index 8eeea5e6058bd..5a8145be74c30 100644 --- a/src/components/FlatList/FlatListWithScrollKey/types.ts +++ b/src/components/FlatList/FlatListWithScrollKey/types.ts @@ -1,8 +1,7 @@ import type {ForwardedRef} from 'react'; -import type {ListRenderItem, FlatList as RNFlatList} from 'react-native'; -import type {CustomFlatListProps} from '@components/FlatList/FlatList/types'; +import type {FlatListProps, ListRenderItem, FlatList as RNFlatList} from 'react-native'; -type BaseFlatListWithScrollKeyProps = Omit, 'data' | 'initialScrollIndex' | 'onContentSizeChange'> & { +type BaseFlatListWithScrollKeyProps = Omit, 'data' | 'initialScrollIndex' | 'onContentSizeChange'> & { data: T[]; initialScrollKey?: string | null | undefined; keyExtractor: (item: T, index: number) => string; @@ -12,6 +11,6 @@ type BaseFlatListWithScrollKeyProps = Omit, 'data' | ' ref: ForwardedRef; }; -type FlatListWithScrollKeyProps = Omit, 'onContentSizeChange'> & Pick, 'onContentSizeChange'>; +type FlatListWithScrollKeyProps = Omit, 'onContentSizeChange'> & Pick, 'onContentSizeChange'>; export type {FlatListWithScrollKeyProps, BaseFlatListWithScrollKeyProps}; diff --git a/src/components/PrevNextButtons.tsx b/src/components/PrevNextButtons.tsx index 0bc38790db297..4af2e6c576b3e 100644 --- a/src/components/PrevNextButtons.tsx +++ b/src/components/PrevNextButtons.tsx @@ -2,7 +2,6 @@ import React from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; -import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; @@ -28,18 +27,17 @@ function PrevNextButtons({isPrevButtonDisabled, isNextButtonDisabled, onNext, on const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'BackArrow']); const styles = useThemeStyles(); const theme = useTheme(); - const {translate} = useLocalize(); return ( { - if (!flatListRef?.current) { - return; - } - - flatListRef.current.scrollToIndex({index}); - }, - [flatListRef], - ); - - /** - * Scroll to the visual bottom of the list. - */ - const scrollToBottom = useCallback(() => { + const scrollToIndex = (index: number) => { if (!flatListRef?.current) { return; } + flatListRef.current.scrollToIndex({index}); + }; - if (isInverted) { - flatListRef.current.scrollToOffset({animated: false, offset: 0}); + /** + * Scroll to the bottom of the inverted FlatList. + * When FlatList is inverted it's "bottom" is really it's top + */ + const scrollToBottom = () => { + if (!flatListRef?.current) { return; } - flatListRef.current.scrollToEnd({animated: false}); - }, [flatListRef, isInverted]); + scrollPositionRef.current = {offset: 0}; + flatListRef.current?.scrollToOffset({animated: false, offset: 0}); + }; /** * Scroll to the end of the FlatList. */ - const scrollToEnd = useCallback(() => { + const scrollToEnd = () => { if (!flatListRef?.current) { return; } @@ -53,18 +46,14 @@ function useReportScrollManager(isInverted = true): ReportScrollManagerData { } flatListRef.current.scrollToEnd({animated: false}); - }, [flatListRef]); - - const scrollToOffset = useCallback( - (offset: number) => { - if (!flatListRef?.current) { - return; - } + }; - flatListRef.current.scrollToOffset({offset, animated: false}); - }, - [flatListRef], - ); + const scrollToOffset = (offset: number) => { + if (!flatListRef?.current) { + return; + } + flatListRef.current.scrollToOffset({offset, animated: false}); + }; return {ref: flatListRef, scrollToIndex, scrollToBottom, scrollToEnd, scrollToOffset}; } diff --git a/src/hooks/useReportScrollManager/index.ts b/src/hooks/useReportScrollManager/index.ts index e8a047e85650e..6b888584887cf 100644 --- a/src/hooks/useReportScrollManager/index.ts +++ b/src/hooks/useReportScrollManager/index.ts @@ -1,8 +1,8 @@ -import {useCallback, useContext} from 'react'; +import {useContext} from 'react'; import {ActionListContext} from '@pages/inbox/ReportScreenContext'; import type ReportScrollManagerData from './types'; -function useReportScrollManager(isInverted = true): ReportScrollManagerData { +function useReportScrollManager(): ReportScrollManagerData { const {flatListRef} = useContext(ActionListContext); /** @@ -17,20 +17,16 @@ function useReportScrollManager(isInverted = true): ReportScrollManagerData { }; /** - * Scroll to the visual bottom of the list. + * Scroll to the bottom of the inverted FlatList. + * When FlatList is inverted it's "bottom" is really it's top */ - const scrollToBottom = useCallback(() => { + const scrollToBottom = () => { if (!flatListRef?.current) { return; } - if (isInverted) { - flatListRef.current.scrollToOffset({animated: false, offset: 0}); - return; - } - - flatListRef.current.scrollToEnd({animated: false}); - }, [flatListRef, isInverted]); + flatListRef.current.scrollToOffset({animated: false, offset: 0}); + }; /** * Scroll to the end of the FlatList. diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index dd514aa46d0ef..737c00574be22 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -553,7 +553,7 @@ function PureReportActionItem({ const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const {transitionActionSheetState} = ActionSheetAwareScrollView.useActionSheetAwareScrollViewActions(); - const {translate, formatPhoneNumber, localeCompare, formatTravelDate, getLocalDateFromDatetime} = useLocalize(); + const {translate, formatPhoneNumber, localeCompare, formatTravelDate, getLocalDateFromDatetime, datetimeToCalendarTime} = useLocalize(); const {showConfirmModal} = useConfirmModal(); const personalDetail = useCurrentUserPersonalDetails(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -2053,6 +2053,12 @@ function PureReportActionItem({ ); }; + // Calculating accessibilityLabel for chat message with sender, date and time and the message content. + const displayName = getDisplayNameOrDefault(personalDetails?.[action.actorAccountID ?? CONST.DEFAULT_NUMBER_ID]); + const formattedTimestamp = datetimeToCalendarTime(action.created, false); + const plainMessage = getReportActionText(action); + const accessibilityLabel = `${displayName}, ${formattedTimestamp}, ${plainMessage}`; + return ( {shouldShowCreatedAction && createdActionContent} @@ -2073,7 +2079,8 @@ function PureReportActionItem({ onSecondaryInteraction={showPopover} preventDefaultContextMenu={draftMessage === undefined && !hasErrors} withoutFocusOnSecondaryInteraction - accessibilityLabel={translate('accessibilityHints.chatMessage')} + accessibilityLabel={accessibilityLabel} + accessibilityHint={translate('accessibilityHints.chatMessage')} accessibilityRole={CONST.ROLE.BUTTON} sentryLabel={CONST.SENTRY_LABEL.REPORT.PURE_REPORT_ACTION_ITEM} > diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index 18caedfe5daaa..6bf8860d99309 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -2,13 +2,13 @@ import type {ListRenderItemInfo} from '@react-native/virtualized-lists'; import {useIsFocused, useRoute} from '@react-navigation/native'; import {isUserValidatedSelector} from '@selectors/Account'; import {tierNameSelector} from '@selectors/UserWallet'; -import React, {memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; import {DeviceEventEmitter, InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {renderScrollComponent as renderActionSheetAwareScrollView} from '@components/ActionSheetAwareScrollView'; import Button from '@components/Button'; -import FlatListWithScrollKey from '@components/FlatList/FlatListWithScrollKey'; +import InvertedFlatList from '@components/FlatList/InvertedFlatList'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -62,6 +62,7 @@ import { import Visibility from '@libs/Visibility'; import type {ReportsSplitNavigatorParamList} from '@navigation/types'; import ConciergeThinkingMessage from '@pages/home/report/ConciergeThinkingMessage'; +import {ActionListContext} from '@pages/inbox/ReportScreenContext'; import variables from '@styles/variables'; import {openReport, readNewestAction, subscribeToNewActionEvent} from '@userActions/Report'; import CONST from '@src/CONST'; @@ -73,7 +74,7 @@ import FloatingMessageCounter from './FloatingMessageCounter'; import getInitialNumToRender from './getInitialNumReportActionsToRender'; import ListBoundaryLoader from './ListBoundaryLoader'; import ReportActionsListItemRenderer from './ReportActionsListItemRenderer'; -import shouldDisplayNewMarkerOnReportAction from './shouldDisplayNewMarkerOnReportAction'; +import {getUnreadMarkerReportAction} from './shouldDisplayNewMarkerOnReportAction'; import StaticReportActionsPreview from './StaticReportActionsPreview'; import useReportUnreadMessageScrollTracking from './useReportUnreadMessageScrollTracking'; @@ -117,6 +118,9 @@ type ReportActionsListProps = { /** ID of the list */ listID: number; + /** Should enable auto scroll to top threshold */ + shouldEnableAutoScrollToTopThreshold?: boolean; + /** Whether the optimistic CREATED report action was added */ hasCreatedActionAdded?: boolean; @@ -167,6 +171,7 @@ function ReportActionsList({ onLayout, isComposerFullSize, listID, + shouldEnableAutoScrollToTopThreshold, parentReportActionForTransactionThread, hasCreatedActionAdded, isConciergeSidePanel, @@ -186,7 +191,8 @@ function ReportActionsList({ const {getLocalDateFromDatetime} = useLocalize(); const {isOffline, lastOfflineAt, lastOnlineAt} = useNetworkWithOfflineStatus(); const route = useRoute>(); - const reportScrollManager = useReportScrollManager(false); + const reportScrollManager = useReportScrollManager(); + const {scrollOffsetRef} = useContext(ActionListContext); const userActiveSince = useRef(DateUtils.getDBTime()); const lastMessageTime = useRef(null); const [isVisible, setIsVisible] = useState(Visibility.isVisible); @@ -229,9 +235,6 @@ function ReportActionsList({ return unsubscribe; }, []); - const scrollingVerticalOffset = useRef(0); - const hasTriggeredTopPagination = useRef(false); - const hasTriggeredBottomPagination = useRef(false); const readActionSkipped = useRef(false); const hasHeaderRendered = useRef(false); const linkedReportActionID = route?.params?.reportActionID; @@ -286,41 +289,18 @@ function ReportActionsList({ /** * The reportActionID the unread marker should display above */ - const [unreadMarkerReportActionID, unreadMarkerReportActionIndex] = useMemo(() => { - if (isAnonymousUser) { - return [null, -1]; - } - - // If there are message that were received while offline, - // we can skip checking all messages later than the earliest received offline message. - const startIndex = earliestReceivedOfflineMessageIndex ?? 0; - - // Scan through each visible report action until we find the appropriate action to show the unread marker - for (let index = startIndex; index < sortedVisibleReportActions.length; index++) { - const reportAction = sortedVisibleReportActions.at(index); - const nextAction = sortedVisibleReportActions.at(index + 1); - const isEarliestReceivedOfflineMessage = index === earliestReceivedOfflineMessageIndex; - - const shouldDisplayNewMarker = - reportAction && - shouldDisplayNewMarkerOnReportAction({ - message: reportAction, - nextMessage: nextAction, - isEarliestReceivedOfflineMessage, - currentUserAccountID, - prevSortedVisibleReportActionsObjects, - unreadMarkerTime, - scrollingVerticalOffset: scrollingVerticalOffset.current, - prevUnreadMarkerReportActionID: prevUnreadMarkerReportActionID.current, - isOffline, - }); - if (shouldDisplayNewMarker) { - return [reportAction.reportActionID, index]; - } - } - - return [null, -1]; - }, [currentUserAccountID, isAnonymousUser, earliestReceivedOfflineMessageIndex, prevSortedVisibleReportActionsObjects, sortedVisibleReportActions, unreadMarkerTime, isOffline]); + const [unreadMarkerReportActionID, unreadMarkerReportActionIndex] = getUnreadMarkerReportAction({ + visibleReportActions: sortedVisibleReportActions, + earliestReceivedOfflineMessageIndex, + currentUserAccountID, + prevSortedVisibleReportActionsObjects, + unreadMarkerTime, + scrollingVerticalOffset: scrollOffsetRef.current, + prevUnreadMarkerReportActionID: prevUnreadMarkerReportActionID.current, + isOffline, + isReversed: false, + isAnonymousUser, + }); prevUnreadMarkerReportActionID.current = unreadMarkerReportActionID; /** @@ -372,53 +352,14 @@ function ReportActionsList({ hasNewestReportActionRef.current = hasNewestReportAction; const sortedVisibleReportActionsRef = useRef(sortedVisibleReportActions); - const maybeLoadChatsOnScroll = useCallback( - (event: NativeSyntheticEvent) => { - const { - contentOffset: {y}, - contentSize: {height: contentHeight}, - layoutMeasurement: {height: layoutHeight}, - } = event.nativeEvent; - - const distanceFromTop = y; - const distanceFromBottom = contentHeight - layoutHeight - y; - - if (distanceFromTop <= 0) { - if (!hasTriggeredTopPagination.current) { - hasTriggeredTopPagination.current = true; - loadNewerChats(false); - } - } else { - hasTriggeredTopPagination.current = false; - } - - if (distanceFromBottom <= 0) { - if (!hasTriggeredBottomPagination.current) { - hasTriggeredBottomPagination.current = true; - if (!isSearchTopmostFullScreenRoute()) { - loadOlderChats(false); - return; - } - - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => requestAnimationFrame(() => loadOlderChats(false))); - } - } else { - hasTriggeredBottomPagination.current = false; - } - }, - [loadNewerChats, loadOlderChats], - ); - const {isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling, onViewableItemsChanged} = useReportUnreadMessageScrollTracking({ reportID: report.reportID, - currentVerticalScrollingOffsetRef: scrollingVerticalOffset, + currentVerticalScrollingOffsetRef: scrollOffsetRef, readActionSkippedRef: readActionSkipped, unreadMarkerReportActionIndex, - isInverted: false, + isInverted: true, onTrackScrolling: (event: NativeSyntheticEvent) => { - scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y; - maybeLoadChatsOnScroll(event); + scrollOffsetRef.current = event.nativeEvent.contentOffset.y; onScroll?.(event); if (shouldScrollToEndAfterLayout && (!hasCreatedActionAdded || isOffline)) { setShouldScrollToEndAfterLayout(false); @@ -429,7 +370,7 @@ function ReportActionsList({ useScrollToEndOnNewMessageReceived({ sizeChangeType: 'changed', - scrollOffsetRef: scrollingVerticalOffset, + scrollOffsetRef, lastActionID: lastAction?.reportActionID, visibleActionsLength: sortedVisibleReportActions.length, hasNewestReportAction, @@ -461,7 +402,7 @@ function ReportActionsList({ // Currently, there's no programmatic way to dismiss the notification center panel. // To handle this, we use the 'referrer' parameter to check if the current navigation is triggered from a notification. const isFromNotification = route?.params?.referrer === CONST.REFERRER.NOTIFICATION; - if ((isVisible || isFromNotification) && scrollingVerticalOffset.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD) { + if ((isVisible || isFromNotification) && scrollOffsetRef.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD) { readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions); if (isFromNotification) { Navigation.setParams({referrer: undefined}); @@ -492,7 +433,7 @@ function ReportActionsList({ lastMessageTime.current = null; const isArchivedReport = isArchivedNonExpenseReport(report, isReportArchived); - const hasNewMessagesInView = scrollingVerticalOffset.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD; + const hasNewMessagesInView = scrollOffsetRef.current < CONST.REPORT.ACTIONS.ACTION_VISIBLE_THRESHOLD; const hasUnreadReportAction = sortedVisibleReportActions.some( (reportAction) => newMessageTimeReference && @@ -808,6 +749,7 @@ function ReportActionsList({ styles, translate, expensifyIcons.UpArrow, + isOffline, ], ); @@ -859,15 +801,7 @@ function ReportActionsList({ ); }, [canShowHeader, report, retryLoadNewerChatsError]); - const shouldShowSkeleton = useMemo( - () => (isOffline || !reportMetadata?.hasOnceLoadedReportActions) && !sortedVisibleReportActions.some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED), - [isOffline, reportMetadata?.hasOnceLoadedReportActions, sortedVisibleReportActions], - ); - - // While reportActions are loading, use the last reportAction as a fallback - // since it’s available before the rest finish loading. - const reportActionsToRender = useMemo(() => (shouldShowSkeleton ? sortedVisibleReportActions.slice(0, 1) : sortedVisibleReportActions), [shouldShowSkeleton, sortedVisibleReportActions]); - const flatListData = useMemo(() => [...reportActionsToRender].reverse(), [reportActionsToRender]); + const shouldShowSkeleton = isOffline && !sortedVisibleReportActions.some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED); const listFooterComponent = useMemo(() => { if (!shouldShowSkeleton) { @@ -897,13 +831,19 @@ function ReportActionsList({ ); }, [hideComposer, initialNumToRender, renderItem, shouldShowReportRecipientLocalTime, sortedVisibleReportActions, styles]); - const renderFlatListItem = useCallback( - ({item}: ListRenderItemInfo) => { - const originalIndex = sortedVisibleReportActions.findIndex((action) => action.reportActionID === item.reportActionID); - return renderItem({item, index: originalIndex >= 0 ? originalIndex : 0} as ListRenderItemInfo); - }, - [renderItem, sortedVisibleReportActions], - ); + const onStartReached = useCallback(() => { + if (!isSearchTopmostFullScreenRoute()) { + loadNewerChats(false); + return; + } + + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(() => requestAnimationFrame(() => loadNewerChats(false))); + }, [loadNewerChats]); + + const onEndReached = useCallback(() => { + loadOlderChats(false); + }, [loadOlderChats]); return ( <> @@ -917,21 +857,23 @@ function ReportActionsList({ fsClass={reportActionsListFSClass} > {shouldScrollToEndAfterLayout && topReportAction ? renderTopReportActions() : undefined} - { trackVerticalScrolling(undefined); }} diff --git a/src/pages/inbox/report/ReportActionsView.tsx b/src/pages/inbox/report/ReportActionsView.tsx index fa4914d34f867..62c37f038b385 100755 --- a/src/pages/inbox/report/ReportActionsView.tsx +++ b/src/pages/inbox/report/ReportActionsView.tsx @@ -1,5 +1,6 @@ import {useIsFocused, useRoute} from '@react-navigation/native'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {InteractionManager} from 'react-native'; import type {LayoutChangeEvent} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; @@ -128,6 +129,7 @@ function ReportActionsView({ const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS); + const prevTransactionThreadReport = usePrevious(transactionThreadReport); const reportActionID = route?.params?.reportActionID; const prevReportActionID = usePrevious(reportActionID); const reportPreviewAction = useMemo(() => getReportPreviewAction(report.chatReportID, report.reportID), [report.chatReportID, report.reportID]); @@ -136,6 +138,7 @@ function ReportActionsView({ const {shouldUseNarrowLayout} = useResponsiveLayout(); const isFocused = useIsFocused(); + const [isNavigatingToLinkedMessage, setNavigatingToLinkedMessage] = useState(false); const prevShouldUseNarrowLayoutRef = useRef(shouldUseNarrowLayout); const reportID = report.reportID; const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); @@ -265,7 +268,12 @@ function ReportActionsView({ [reportActions, isOffline, canPerformWriteAction, reportTransactionIDs, visibleReportActionsData, reportID], ); + const newestReportAction = useMemo(() => reportActions?.at(0), [reportActions]); const mostRecentIOUReportActionID = useMemo(() => getMostRecentIOURequestActionID(reportActions), [reportActions]); + const lastActionCreated = visibleReportActions.at(0)?.created; + const isNewestAction = (actionCreated: string | undefined, lastVisibleActionCreated: string | undefined) => + actionCreated && lastVisibleActionCreated ? actionCreated >= lastVisibleActionCreated : actionCreated === lastVisibleActionCreated; + const hasNewestReportAction = isNewestAction(lastActionCreated, report.lastVisibleActionCreated) || isNewestAction(lastActionCreated, transactionThreadReport?.lastVisibleActionCreated); const isSingleExpenseReport = reportPreviewAction?.childMoneyRequestCount === 1; const isMissingTransactionThreadReportID = !transactionThreadReport?.reportID; @@ -328,6 +336,33 @@ function ReportActionsView({ [report, onLayout], ); + // Check if the first report action in the list is the one we're currently linked to + const isTheFirstReportActionIsLinked = newestReportAction?.reportActionID === reportActionID; + + useEffect(() => { + let timerID: NodeJS.Timeout; + + if (!isTheFirstReportActionIsLinked && reportActionID) { + setNavigatingToLinkedMessage(true); + // After navigating to the linked reportAction, apply this to correctly set + // `autoscrollToTopThreshold` prop when linking to a specific reportAction. + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(() => { + // Using a short delay to ensure the view is updated after interactions + timerID = setTimeout(() => setNavigatingToLinkedMessage(false), 10); + }); + } else { + setNavigatingToLinkedMessage(false); + } + + return () => { + if (!timerID) { + return; + } + clearTimeout(timerID); + }; + }, [isTheFirstReportActionIsLinked, reportActionID]); + // Show skeleton while loading initial report actions when data is incomplete/missing and online const shouldShowSkeletonForInitialLoad = isLoadingInitialReportActions && (isReportDataIncomplete || isMissingReportActions) && !isOffline; @@ -360,6 +395,7 @@ function ReportActionsView({ } // AutoScroll is disabled when we do linking to a specific reportAction + const shouldEnableAutoScroll = (hasNewestReportAction && (!reportActionID || !isNavigatingToLinkedMessage)) || (transactionThreadReport && !prevTransactionThreadReport); return ( <>