diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js index dc4f35a59cb..de616268507 100644 --- a/src/libs/Navigation/Navigation.js +++ b/src/libs/Navigation/Navigation.js @@ -10,6 +10,7 @@ import linkingConfig from './linkingConfig'; import navigationRef from './navigationRef'; import NAVIGATORS from '../../NAVIGATORS'; import originalGetTopmostReportId from './getTopmostReportId'; +import originalGetTopmostReportActionId from './getTopmostReportActionID'; import getStateFromPath from './getStateFromPath'; import SCREENS from '../../SCREENS'; import CONST from '../../CONST'; @@ -46,6 +47,9 @@ function canNavigate(methodName, params = {}) { // Re-exporting the getTopmostReportId here to fill in default value for state. The getTopmostReportId isn't defined in this file to avoid cyclic dependencies. const getTopmostReportId = (state = navigationRef.getState()) => originalGetTopmostReportId(state); +// Re-exporting the getTopmostReportActionID here to fill in default value for state. The getTopmostReportActionID isn't defined in this file to avoid cyclic dependencies. +const getTopmostReportActionId = (state = navigationRef.getState()) => originalGetTopmostReportActionId(state); + /** * Method for finding on which index in stack we are. * @param {Object} route @@ -268,6 +272,7 @@ export default { setIsNavigationReady, getTopmostReportId, getRouteNameFromStateEvent, + getTopmostReportActionId, }; export {navigationRef}; diff --git a/src/libs/Navigation/getTopmostReportActionID.js b/src/libs/Navigation/getTopmostReportActionID.js new file mode 100644 index 00000000000..a4480931cda --- /dev/null +++ b/src/libs/Navigation/getTopmostReportActionID.js @@ -0,0 +1,42 @@ +import lodashFindLast from 'lodash/findLast'; +import lodashGet from 'lodash/get'; + +// This function is in a separate file than Navigation.js to avoid cyclic dependency. + +/** + * Find the last visited report screen in the navigation state and get the linked reportActionID of it. + * + * @param {Object} state - The react-navigation state + * @returns {String | undefined} - It's possible that there is no report screen + */ +function getTopmostReportActionID(state) { + if (!state) { + return; + } + const topmostCentralPane = lodashFindLast(state.routes, (route) => route.name === 'CentralPaneNavigator'); + + if (!topmostCentralPane) { + return; + } + + const directReportActionIDParam = lodashGet(topmostCentralPane, 'params.params.reportActionID'); + + if (!topmostCentralPane.state && !directReportActionIDParam) { + return; + } + + if (directReportActionIDParam) { + return directReportActionIDParam; + } + + const topmostReport = lodashFindLast(topmostCentralPane.state.routes, (route) => route.name === 'Report'); + if (!topmostReport) { + return; + } + + const topmostReportActionID = lodashGet(topmostReport, 'params.reportActionID'); + + return topmostReportActionID; +} + +export default getTopmostReportActionID; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index fe1dcf248f9..b21fa501990 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; -import React, {useState, useRef, useEffect, memo, useCallback, useContext} from 'react'; +import React, {useState, useRef, useEffect, memo, useCallback, useContext, useMemo} from 'react'; import {InteractionManager, View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -61,12 +61,13 @@ import * as Session from '../../../libs/actions/Session'; import MoneyRequestView from '../../../components/ReportActionItem/MoneyRequestView'; import {hideContextMenu} from './ContextMenu/ReportActionContextMenu'; import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; -import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import * as store from '../../../libs/actions/ReimbursementAccount/store'; import * as BankAccounts from '../../../libs/actions/BankAccounts'; import {ReactionListContext} from '../ReportScreenContext'; import usePrevious from '../../../hooks/usePrevious'; import Permissions from '../../../libs/Permissions'; +import themeColors from '../../../styles/themes/default'; +import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import RenderHTML from '../../../components/RenderHTML'; import ReportAttachmentsContext from './ReportAttachmentsContext'; @@ -138,6 +139,9 @@ function ReportActionItem(props) { const prevDraftMessage = usePrevious(props.draftMessage); const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action); const originalReport = props.report.reportID === originalReportID ? props.report : ReportUtils.getReport(originalReportID); + const isReportActionLinked = props.linkedReportActionID === props.action.reportActionID; + + const highlightedBackgroundColorIfNeeded = useMemo(() => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(themeColors.highlightBG) : {}), [isReportActionLinked]); // When active action changes, we need to update the `isContextMenuActive` state const isActiveReportActionForMenu = ReportActionContextMenu.isActiveReportAction(props.action.reportActionID); @@ -594,7 +598,7 @@ function ReportActionItem(props) { disabled={Boolean(props.draftMessage)} > {(hovered) => ( - + {props.shouldDisplayNewMarker && } )} - {renderReportActionItem(hovered, isWhisper, hasErrors)} + {renderReportActionItem(hovered || isReportActionLinked, isWhisper, hasErrors)} @@ -716,6 +720,7 @@ export default compose( prevProps.report.managerID === nextProps.report.managerID && prevProps.report.managerEmail === nextProps.report.managerEmail && prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && - lodashGet(prevProps.report, 'total', 0) === lodashGet(nextProps.report, 'total', 0), + lodashGet(prevProps.report, 'total', 0) === lodashGet(nextProps.report, 'total', 0) && + prevProps.linkedReportActionID === nextProps.linkedReportActionID, ), ); diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 0163a7ff2b4..438b6e9b68d 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -2,6 +2,8 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import _ from 'underscore'; +import {useRoute} from '@react-navigation/native'; +import lodashGet from 'lodash/get'; import CONST from '../../../CONST'; import InvertedFlatList from '../../../components/InvertedFlatList'; import {withPersonalDetails} from '../../../components/OnyxProvider'; @@ -117,6 +119,7 @@ function ReportActionsList({ const reportScrollManager = useReportScrollManager(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); + const route = useRoute(); const opacity = useSharedValue(0); const userActiveSince = useRef(null); const [currentUnreadMarker, setCurrentUnreadMarker] = useState(null); @@ -124,6 +127,7 @@ function ReportActionsList({ const readActionSkipped = useRef(false); const reportActionSize = useRef(sortedReportActions.length); const firstRenderRef = useRef(true); + const linkedReportActionID = lodashGet(route, 'params.reportActionID', ''); // This state is used to force a re-render when the user manually marks a message as unread // by using a timestamp you can force re-renders without having to worry about if another message was marked as unread before @@ -303,6 +307,7 @@ function ReportActionsList({ reportAction={reportAction} index={index} report={report} + linkedReportActionID={linkedReportActionID} hasOutstandingIOU={hasOutstandingIOU} sortedReportActions={sortedReportActions} mostRecentIOUReportActionID={mostRecentIOUReportActionID} @@ -311,7 +316,7 @@ function ReportActionsList({ /> ); }, - [report, hasOutstandingIOU, sortedReportActions, mostRecentIOUReportActionID, messageManuallyMarkedUnread, shouldHideThreadDividerLine, currentUnreadMarker], + [report, linkedReportActionID, hasOutstandingIOU, sortedReportActions, mostRecentIOUReportActionID, messageManuallyMarkedUnread, shouldHideThreadDividerLine, currentUnreadMarker], ); // Native mobile does not render updates flatlist the changes even though component did update called. diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index f70714076ad..40b9ee9142b 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -33,11 +33,15 @@ const propTypes = { /** Should we display the new marker on top of the comment? */ shouldDisplayNewMarker: PropTypes.bool.isRequired, + + /** Linked report action ID */ + linkedReportActionID: PropTypes.string, }; const defaultProps = { mostRecentIOUReportActionID: '', hasOutstandingIOU: false, + linkedReportActionID: '', }; function ReportActionsListItemRenderer({ @@ -49,6 +53,7 @@ function ReportActionsListItemRenderer({ mostRecentIOUReportActionID, shouldHideThreadDividerLine, shouldDisplayNewMarker, + linkedReportActionID, }) { const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && @@ -67,6 +72,7 @@ function ReportActionsListItemRenderer({ shouldHideThreadDividerLine={shouldHideThreadDividerLine} report={report} action={reportAction} + linkedReportActionID={linkedReportActionID} displayAsGroup={ReportActionsUtils.isConsecutiveActionMadeByPreviousActor(sortedReportActions, index)} shouldDisplayNewMarker={shouldDisplayNewMarker} shouldShowSubscriptAvatar={ diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 984654c6b50..2b82650a072 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -141,7 +141,12 @@ class SidebarLinks extends React.PureComponent { // or when clicking the active LHN row on large screens // or when continuously clicking different LHNs, only apply to small screen // since getTopmostReportId always returns on other devices - if (this.props.isCreateMenuOpen || option.reportID === Navigation.getTopmostReportId() || (this.props.isSmallScreenWidth && this.props.isActiveReport(option.reportID))) { + const reportActionID = Navigation.getTopmostReportActionId(); + if ( + this.props.isCreateMenuOpen || + (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || + (this.props.isSmallScreenWidth && this.props.isActiveReport(option.reportID) && !reportActionID) + ) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID));