diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index fd4f3e13dac8..c0e831314bcf 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -62,6 +62,18 @@ Onyx.connect({ }, }); +const currentReportData = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + callback: (data, key) => { + if (!key || !data) { + return; + } + const reportID = CollectionUtils.extractCollectionItemID(key); + currentReportData[reportID] = data; + }, +}); + let isNetworkOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -373,6 +385,10 @@ function addComment(reportID, text) { addActions(reportID, text); } +function reportActionsExist(reportID) { + return allReportActions[reportID] !== undefined; +} + /** * Gets the latest page of report actions and updates the last read message * If a chat with the passed reportID is not found, we will create a chat based on the passed participantList @@ -388,11 +404,13 @@ function openReport(reportID, participantLoginList = [], newReportObject = {}, p const optimisticReportData = { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - isLoadingReportActions: true, - isLoadingMoreReportActions: false, - lastReadTime: DateUtils.getDBTime(), - }, + value: reportActionsExist(reportID) + ? {} + : { + isLoadingReportActions: true, + isLoadingMoreReportActions: false, + reportName: lodashGet(allReports, [reportID, 'reportName'], CONST.REPORT.DEFAULT_REPORT_NAME), + }, }; const reportSuccessData = { onyxMethod: Onyx.METHOD.MERGE, @@ -520,6 +538,8 @@ function openReport(reportID, participantLoginList = [], newReportObject = {}, p } } + params.clientLastReadTime = lodashGet(currentReportData, [reportID, 'lastReadTime'], ''); + if (isFromDeepLink) { // eslint-disable-next-line rulesdir/no-api-side-effects-method API.makeRequestWithSideEffects('OpenReport', params, onyxData).finally(() => { @@ -720,10 +740,12 @@ function expandURLPreview(reportID, reportActionID) { * @param {String} reportID */ function readNewestAction(reportID) { + const lastReadTime = DateUtils.getDBTime(); API.write( 'ReadNewestAction', { reportID, + lastReadTime, }, { optimisticData: [ @@ -731,7 +753,7 @@ function readNewestAction(reportID) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { - lastReadTime: DateUtils.getDBTime(), + lastReadTime, }, }, ], @@ -1749,6 +1771,10 @@ function openReportFromDeepLink(url, isAuthenticated) { }); } +function getCurrentUserAccountID() { + return currentUserAccountID; +} + /** * Leave a report by setting the state to submitted and closed * @@ -1948,6 +1974,7 @@ export { hasAccountIDEmojiReacted, shouldShowReportActionNotification, leaveRoom, + getCurrentUserAccountID, setLastOpenedPublicRoom, flagComment, openLastOpenedPublicRoom, diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 25b22ed409a2..a28444b67560 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -215,7 +215,6 @@ class ReportScreen extends React.Component { if (this.props.report.reportID && this.props.report.reportID === getReportID(this.props.route)) { return; } - Report.openReport(reportIDFromPath); } diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index ada4c464da15..7f897ee825fb 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -1,14 +1,15 @@ import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useState, useRef, useMemo} from 'react'; import Animated, {useSharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated'; import _ from 'underscore'; import InvertedFlatList from '../../../components/InvertedFlatList'; import compose from '../../../libs/compose'; import styles from '../../../styles/styles'; import * as ReportUtils from '../../../libs/ReportUtils'; +import * as Report from '../../../libs/actions/Report'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../components/withCurrentUserPersonalDetails'; -import {withNetwork, withPersonalDetails} from '../../../components/OnyxProvider'; +import {withPersonalDetails} from '../../../components/OnyxProvider'; import ReportActionItem from './ReportActionItem'; import ReportActionItemParentAction from './ReportActionItemParentAction'; import ReportActionsSkeletonView from '../../../components/ReportActionsSkeletonView'; @@ -17,14 +18,13 @@ import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; import reportActionPropTypes from './reportActionPropTypes'; import CONST from '../../../CONST'; import reportPropTypes from '../../reportPropTypes'; -import networkPropTypes from '../../../components/networkPropTypes'; -import withLocalize from '../../../components/withLocalize'; +import useLocalize from '../../../hooks/useLocalize'; +import useNetwork from '../../../hooks/useNetwork'; +import DateUtils from '../../../libs/DateUtils'; +import FloatingMessageCounter from './FloatingMessageCounter'; import useReportScrollManager from '../../../hooks/useReportScrollManager'; const propTypes = { - /** Position of the "New" line marker */ - newMarkerReportActionID: PropTypes.string, - /** The report currently being looked at */ report: reportPropTypes.isRequired, @@ -41,14 +41,11 @@ const propTypes = { onLayout: PropTypes.func.isRequired, /** Callback executed on scroll */ - onScroll: PropTypes.func.isRequired, + onScroll: PropTypes.func, /** Function to load more chats */ loadMoreChats: PropTypes.func.isRequired, - /** Information about the network */ - network: networkPropTypes.isRequired, - /** The policy object for the current route */ policy: PropTypes.shape({ /** The name of the policy */ @@ -63,13 +60,20 @@ const propTypes = { }; const defaultProps = { - newMarkerReportActionID: '', personalDetails: {}, + onScroll: () => {}, mostRecentIOUReportActionID: '', isLoadingMoreReportActions: false, ...withCurrentUserPersonalDetailsDefaultProps, }; +const VERTICAL_OFFSET_THRESHOLD = 200; +const MSG_VISIBLE_THRESHOLD = 250; + +// Seems that there is an architecture issue that prevents us from using the reportID with useRef +// the useRef value gets reset when the reportID changes, so we use a global variable to keep track +let prevReportID = null; + /** * Create a unique key for each action in the FlatList. * We use the reportActionID that is a string representation of a random 64-bit int, which should be @@ -82,36 +86,136 @@ function keyExtractor(item) { return item.reportActionID; } -function ReportActionsList(props) { +function isMessageUnread(message, lastReadTime) { + return Boolean(message && lastReadTime && message.created && lastReadTime < message.created); +} + +function ReportActionsList({ + report, + sortedReportActions, + windowHeight, + onScroll, + mostRecentIOUReportActionID, + isSmallScreenWidth, + personalDetailsList, + currentUserPersonalDetails, + hasOutstandingIOU, + loadMoreChats, + onLayout, + isComposerFullSize, +}) { const reportScrollManager = useReportScrollManager(); + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); const opacity = useSharedValue(0); + const userActiveSince = useRef(null); + const currentUnreadMarker = useRef(null); + const scrollingVerticalOffset = useRef(0); + const readActionSkipped = useRef(false); + const reportActionSize = useRef(sortedReportActions.length); + + // Considering that renderItem is enclosed within a useCallback, marking it as "read" twice will retain the value as "true," preventing the useCallback from re-executing. + // However, if we create and listen to an object, it will lead to a new useCallback execution. + const [messageManuallyMarked, setMessageManuallyMarked] = useState({read: false}); + const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(false); const animatedStyles = useAnimatedStyle(() => ({ opacity: opacity.value, })); + useEffect(() => { opacity.value = withTiming(1, {duration: 100}); }, [opacity]); const [skeletonViewHeight, setSkeletonViewHeight] = useState(0); - const windowHeight = props.windowHeight; + useEffect(() => { + // If the reportID changes, we reset the userActiveSince to null, we need to do it because + // the parent component is sending the previous reportID even when the user isn't active + // on the report + if (userActiveSince.current && prevReportID && prevReportID !== report.reportID) { + userActiveSince.current = null; + } else { + userActiveSince.current = DateUtils.getDBTime(); + } + prevReportID = report.reportID; + }, [report.reportID]); + + useEffect(() => { + if (!userActiveSince.current || report.reportID !== prevReportID) { + return; + } + + if (ReportUtils.isUnread(report)) { + if (scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD) { + Report.readNewestAction(report.reportID); + } else { + readActionSkipped.current = true; + } + } + + if (currentUnreadMarker.current || reportActionSize.current === sortedReportActions.length) { + return; + } + + reportActionSize.current = sortedReportActions.length; + currentUnreadMarker.current = null; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sortedReportActions.length, report.reportID]); + + useEffect(() => { + const didManuallyMarkReportAsUnread = report.lastReadTime < DateUtils.getDBTime() && ReportUtils.isUnread(report); + if (!didManuallyMarkReportAsUnread) { + setMessageManuallyMarked({read: false}); + return; + } + + // Clearing the current unread marker so that it can be recalculated + currentUnreadMarker.current = null; + setMessageManuallyMarked({read: true}); + + // We only care when a new lastReadTime is set in the report + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [report.lastReadTime]); + + /** + * Show/hide the new floating message counter when user is scrolling back/forth in the history of messages. + */ + const handleUnreadFloatingButton = () => { + if (scrollingVerticalOffset.current > VERTICAL_OFFSET_THRESHOLD && !isFloatingMessageCounterVisible && !!currentUnreadMarker.current) { + setIsFloatingMessageCounterVisible(true); + } + + if (scrollingVerticalOffset.current < VERTICAL_OFFSET_THRESHOLD && isFloatingMessageCounterVisible) { + if (readActionSkipped.current) { + readActionSkipped.current = false; + Report.readNewestAction(report.reportID); + } + setIsFloatingMessageCounterVisible(false); + } + }; + + const trackVerticalScrolling = (event) => { + scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y; + handleUnreadFloatingButton(); + onScroll(event); + }; + + const scrollToBottomAndMarkReportAsRead = () => { + reportScrollManager.scrollToBottom(); + readActionSkipped.current = false; + Report.readNewestAction(report.reportID); + }; /** * Calculates the ideal number of report actions to render in the first render, based on the screen height and on * the height of the smallest report action possible. * @return {Number} */ - const calculateInitialNumToRender = useCallback(() => { + const initialNumToRender = useMemo(() => { const minimumReportActionHeight = styles.chatItem.paddingTop + styles.chatItem.paddingBottom + variables.fontSizeNormalHeight; const availableHeight = windowHeight - (CONST.CHAT_FOOTER_MIN_HEIGHT + variables.contentHeaderHeight); return Math.ceil(availableHeight / minimumReportActionHeight); }, [windowHeight]); - const report = props.report; - const hasOutstandingIOU = props.report.hasOutstandingIOU; - const newMarkerReportActionID = props.newMarkerReportActionID; - const sortedReportActions = props.sortedReportActions; - const mostRecentIOUReportActionID = props.mostRecentIOUReportActionID; - /** * @param {Object} args * @param {Number} args.index @@ -119,13 +223,31 @@ function ReportActionsList(props) { */ const renderItem = useCallback( ({item: reportAction, index}) => { - // When the new indicator should not be displayed we explicitly set it to null - const shouldDisplayNewMarker = reportAction.reportActionID === newMarkerReportActionID; + let shouldDisplayNewMarker = false; + + if (!currentUnreadMarker.current) { + const nextMessage = sortedReportActions[index + 1]; + const isCurrentMessageUnread = isMessageUnread(reportAction, report.lastReadTime); + shouldDisplayNewMarker = isCurrentMessageUnread && !isMessageUnread(nextMessage, report.lastReadTime); + + if (!messageManuallyMarked.read) { + shouldDisplayNewMarker = shouldDisplayNewMarker && reportAction.actorAccountID !== Report.getCurrentUserAccountID(); + } + const canDisplayMarker = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < userActiveSince.current : true; + + if (!currentUnreadMarker.current && shouldDisplayNewMarker && canDisplayMarker) { + currentUnreadMarker.current = reportAction.reportActionID; + } + } else { + shouldDisplayNewMarker = reportAction.reportActionID === currentUnreadMarker.current; + } + const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && ReportUtils.isChatThread(report) && !ReportActionsUtils.isTransactionThread(ReportActionsUtils.getParentReportAction(report)); - const shouldHideThreadDividerLine = sortedReportActions.length > 1 && sortedReportActions[sortedReportActions.length - 2].reportActionID === newMarkerReportActionID; + const shouldHideThreadDividerLine = sortedReportActions.length > 1 && sortedReportActions[sortedReportActions.length - 2].reportActionID === currentUnreadMarker.current; + return shouldDisplayParentAction ? ( ); }, - [report, hasOutstandingIOU, newMarkerReportActionID, sortedReportActions, mostRecentIOUReportActionID], + [report, hasOutstandingIOU, sortedReportActions, mostRecentIOUReportActionID, messageManuallyMarked], ); // Native mobile does not render updates flatlist the changes even though component did update called. // To notify there something changes we can use extraData prop to flatlist - const extraData = [props.isSmallScreenWidth ? props.newMarkerReportActionID : undefined, ReportUtils.isArchivedRoom(props.report)]; - const hideComposer = ReportUtils.shouldDisableWriteActions(props.report); - const shouldShowReportRecipientLocalTime = - ReportUtils.canShowReportRecipientLocalTime(props.personalDetails, props.report, props.currentUserPersonalDetails.accountID) && !props.isComposerFullSize; + const extraData = [isSmallScreenWidth ? currentUnreadMarker.current : undefined, ReportUtils.isArchivedRoom(report)]; + const hideComposer = ReportUtils.shouldDisableWriteActions(report); + const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(personalDetailsList, report, currentUserPersonalDetails.accountID) && !isComposerFullSize; return ( - - { - if (props.report.isLoadingMoreReportActions) { - return ; - } - - // Make sure the oldest report action loaded is not the first. This is so we do not show the - // skeleton view above the created action in a newly generated optimistic chat or one with not - // that many comments. - const lastReportAction = _.last(props.sortedReportActions) || {}; - if (props.report.isLoadingReportActions && lastReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) { - return ( - - ); - } - return null; - }} - keyboardShouldPersistTaps="handled" - onLayout={(event) => { - setSkeletonViewHeight(event.nativeEvent.layout.height); - props.onLayout(event); - }} - onScroll={props.onScroll} - extraData={extraData} + <> + - + + { + if (report.isLoadingMoreReportActions) { + return ; + } + + // Make sure the oldest report action loaded is not the first. This is so we do not show the + // skeleton view above the created action in a newly generated optimistic chat or one with not + // that many comments. + const lastReportAction = _.last(sortedReportActions) || {}; + if (report.isLoadingReportActions && lastReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) { + return ( + + ); + } + + return null; + }} + keyboardShouldPersistTaps="handled" + onLayout={(event) => { + setSkeletonViewHeight(event.nativeEvent.layout.height); + onLayout(event); + }} + onScroll={trackVerticalScrolling} + extraData={extraData} + /> + + ); } @@ -208,4 +336,4 @@ ReportActionsList.propTypes = propTypes; ReportActionsList.defaultProps = defaultProps; ReportActionsList.displayName = 'ReportActionsList'; -export default compose(withWindowDimensions, withLocalize, withPersonalDetails(), withNetwork(), withCurrentUserPersonalDetails)(ReportActionsList); +export default compose(withWindowDimensions, withPersonalDetails(), withCurrentUserPersonalDetails)(ReportActionsList); diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 8ba49c179dc3..da475e61f749 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -1,26 +1,21 @@ -import React, {useRef, useState, useEffect, useContext, useMemo, useCallback} from 'react'; +import React, {useRef, useEffect, useContext, useMemo} from 'react'; import PropTypes from 'prop-types'; import _ from 'underscore'; import lodashGet from 'lodash/get'; -import lodashCloneDeep from 'lodash/cloneDeep'; import {useIsFocused} from '@react-navigation/native'; import * as Report from '../../../libs/actions/Report'; import reportActionPropTypes from './reportActionPropTypes'; -import Visibility from '../../../libs/Visibility'; import Timing from '../../../libs/actions/Timing'; import CONST from '../../../CONST'; import compose from '../../../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import useCopySelectionHelper from '../../../hooks/useCopySelectionHelper'; -import useReportScrollManager from '../../../hooks/useReportScrollManager'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import Performance from '../../../libs/Performance'; import {withNetwork} from '../../../components/OnyxProvider'; -import FloatingMessageCounter from './FloatingMessageCounter'; import networkPropTypes from '../../../components/networkPropTypes'; import ReportActionsList from './ReportActionsList'; import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; -import * as ReportUtils from '../../../libs/ReportUtils'; import reportPropTypes from '../../reportPropTypes'; import PopoverReactionList from './ReactionList/PopoverReactionList'; import getIsReportFullyVisible from '../../../libs/getIsReportFullyVisible'; @@ -58,40 +53,16 @@ const defaultProps = { policy: null, }; -// In the component we are subscribing to the arrival of new actions. -// As there is the possibility that there are multiple instances of a ReportScreen -// for the same report, we only ever want one subscription to be active, as -// the subscriptions could otherwise be conflicting. -const newActionUnsubscribeMap = {}; - function ReportActionsView(props) { const context = useContext(ReportScreenContext); useCopySelectionHelper(); - const {scrollToBottom} = useReportScrollManager(); - const didLayout = useRef(false); const didSubscribeToReportTypingEvents = useRef(false); const hasCachedActions = useRef(_.size(props.reportActions) > 0); - const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(false); - - // We use the newMarkerReport ID in a network subscription, we don't want to constantly re-create - // the subscription (as we want to avoid loosing events), so we use a ref to store the value in addition. - // As the value is also needed for UI updates, we also store it in state. - const [newMarkerReportActionID, _setNewMarkerReportActionID] = useState(ReportUtils.getNewMarkerReportActionID(props.report, props.reportActions)); - const newMarkerReportActionIDRef = useRef(newMarkerReportActionID); - const setNewMarkerReportActionID = useCallback((value) => { - newMarkerReportActionIDRef.current = value; - _setNewMarkerReportActionID(value); - }, []); - - const currentScrollOffset = useRef(0); const mostRecentIOUReportActionID = useRef(ReportActionsUtils.getMostRecentIOURequestActionID(props.reportActions)); - - const prevReportActionsRef = useRef(props.reportActions); - const prevReportRef = useRef(props.report); const prevNetworkRef = useRef(props.network); const prevIsSmallScreenWidthRef = useRef(props.isSmallScreenWidth); @@ -112,85 +83,11 @@ function ReportActionsView(props) { Report.openReport(reportID); }; - useEffect(() => { - const unsubscribeVisibilityListener = Visibility.onVisibilityChange(() => { - if (!isReportFullyVisible) { - return; - } - // If the app user becomes active and they have no unread actions we clear the new marker to sync their device - // e.g. they could have read these messages on another device and only just become active here - const hasUnreadActions = ReportUtils.isUnread(props.report); - if (!hasUnreadActions) { - setNewMarkerReportActionID(''); - } - }); - return () => { - if (!unsubscribeVisibilityListener) { - return; - } - unsubscribeVisibilityListener(); - }; - }, [isReportFullyVisible, isFocused, props.report, setNewMarkerReportActionID]); - useEffect(() => { openReportIfNecessary(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - // Why are we doing this, when in the cleanup of the useEffect we are already calling the unsubscribe function? - // Answer: On web, when navigating to another report screen, the previous report screen doesn't get unmounted, - // meaning that the cleanup might not get called. When we then open a report we had open already previosuly, a new - // ReportScreen will get created. Thus, we have to cancel the earlier subscription of the previous screen, - // because the two subscriptions could conflict! - // In case we return to the previous screen (e.g. by web back navigation) the useEffect for that screen would - // fire again, as the focus has changed and will set up the subscription correctly again. - const previousSubUnsubscribe = newActionUnsubscribeMap[reportID]; - if (previousSubUnsubscribe) { - previousSubUnsubscribe(); - } - - // This callback is triggered when a new action arrives via Pusher and the event is emitted from Report.js. This allows us to maintain - // a single source of truth for the "new action" event instead of trying to derive that a new action has appeared from looking at props. - const unsubscribe = Report.subscribeToNewActionEvent(reportID, (isFromCurrentUser, newActionID) => { - const isNewMarkerReportActionIDSet = !_.isEmpty(newMarkerReportActionIDRef.current); - - // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where - // they are now in the list. - if (isFromCurrentUser) { - scrollToBottom(); - // If the current user sends a new message in the chat we clear the new marker since they have "read" the report - setNewMarkerReportActionID(''); - } else if (isReportFullyVisible) { - // We use the scroll position to determine whether the report should be marked as read and the new line indicator reset. - // If the user is scrolled up and no new line marker is set we will set it otherwise we will do nothing so the new marker - // stays in it's previous position. - if (currentScrollOffset.current === 0) { - Report.readNewestAction(reportID); - setNewMarkerReportActionID(''); - } else if (!isNewMarkerReportActionIDSet) { - // The report is not in view and we received a comment from another user while the new marker is not set - // so we will set the new marker now. - setNewMarkerReportActionID(newActionID); - } - } else if (!isNewMarkerReportActionIDSet) { - setNewMarkerReportActionID(newActionID); - } - }); - const cleanup = () => { - if (unsubscribe) { - unsubscribe(); - } - Report.unsubscribeFromReportChannel(reportID); - }; - - newActionUnsubscribeMap[reportID] = cleanup; - - return () => { - cleanup(); - }; - }, [isReportFullyVisible, reportID, scrollToBottom, setNewMarkerReportActionID]); - useEffect(() => { const prevNetwork = prevNetworkRef.current; // When returning from offline to online state we want to trigger a request to OpenReport which @@ -216,7 +113,6 @@ function ReportActionsView(props) { const didScreenSizeIncrease = prevIsSmallScreenWidth && !props.isSmallScreenWidth; const didReportBecomeVisible = isReportFullyVisible && didScreenSizeIncrease; if (didReportBecomeVisible) { - setNewMarkerReportActionID(ReportUtils.isUnread(props.report) ? ReportUtils.getNewMarkerReportActionID(props.report, props.reportActions) : ''); openReportIfNecessary(); } // update ref with current state @@ -224,47 +120,6 @@ function ReportActionsView(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.isSmallScreenWidth, props.report, props.reportActions, isReportFullyVisible]); - useEffect(() => { - const prevReportActions = prevReportActionsRef.current; - // If the report is unread, we want to check if the number of actions has decreased. If so, then it seems that one of them was deleted. In this case, if the deleted action was the - // one marking the unread point, we need to recalculate which action should be the unread marker. - if (prevReportActions && ReportUtils.isUnread(props.report) && prevReportActions.length > props.report.length) - setNewMarkerReportActionID(ReportUtils.getNewMarkerReportActionID(props.report, props.reportActions)); - - prevReportActionsRef.current = props.reportActions; - }, [props.report, props.reportActions, setNewMarkerReportActionID]); - - useEffect(() => { - // If the last unread message was deleted, remove the *New* green marker and the *New Messages* notification at scroll just as the deletion starts. - if ( - !( - ReportUtils.isUnread(props.report) && - props.reportActions.length > 0 && - props.reportActions[0].pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && - !props.network.isOffline - ) - ) { - return; - } - const reportActionsWithoutPendingOne = lodashCloneDeep(props.reportActions); - reportActionsWithoutPendingOne.shift(); - if (newMarkerReportActionID !== ReportUtils.getNewMarkerReportActionID(props.report, reportActionsWithoutPendingOne)) { - setNewMarkerReportActionID(ReportUtils.getNewMarkerReportActionID(props.report, reportActionsWithoutPendingOne)); - } - }, [props.report, props.reportActions, props.network, newMarkerReportActionID, setNewMarkerReportActionID]); - - useEffect(() => { - const prevReport = prevReportRef.current; - // Checks to see if a report comment has been manually "marked as unread". All other times when the lastReadTime - // changes it will be because we marked the entire report as read. - const didManuallyMarkReportAsUnread = prevReport && prevReport.lastReadTime !== props.report.lastReadTime && ReportUtils.isUnread(props.report); - if (didManuallyMarkReportAsUnread) { - setNewMarkerReportActionID(ReportUtils.getNewMarkerReportActionID(props.report, props.reportActions)); - } - // update ref with current report - prevReportRef.current = props.report; - }, [props.report, props.reportActions, setNewMarkerReportActionID]); - useEffect(() => { // Ensures subscription event succeeds when the report/workspace room is created optimistically. // Check if the optimistic `OpenReport` or `AddWorkspaceRoom` has succeeded by confirming @@ -298,33 +153,6 @@ function ReportActionsView(props) { Report.readOldestAction(reportID, oldestReportAction.reportActionID); }; - const scrollToBottomAndMarkReportAsRead = () => { - scrollToBottom(); - Report.readNewestAction(reportID); - }; - - /** - * Show/hide the new floating message counter when user is scrolling back/forth in the history of messages. - */ - const toggleFloatingMessageCounter = () => { - if (currentScrollOffset.current < -200 && !isFloatingMessageCounterVisible) { - setIsFloatingMessageCounterVisible(true); - } - - if (currentScrollOffset.current > -200 && isFloatingMessageCounterVisible) { - setIsFloatingMessageCounterVisible(false); - } - }; - - /** - * keeps track of the Scroll offset of the main messages list - * - * @param {Object} {nativeEvent} - */ - const trackScroll = ({nativeEvent}) => { - currentScrollOffset.current = -nativeEvent.contentOffset.y; - toggleFloatingMessageCounter(); - }; /** * Runs when the FlatList finishes laying out */ @@ -352,19 +180,13 @@ function ReportActionsView(props) { return ( <> - diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 9f6ac56e052b..c06d3bc83766 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -252,6 +252,7 @@ describe('actions/Report', () => { jest.advanceTimersByTime(10); currentTime = DateUtils.getDBTime(); Report.openReport(REPORT_ID); + Report.readNewestAction(REPORT_ID); return waitForPromisesToResolve(); }) .then(() => { diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index 3986dec082bf..1666ffb87400 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -438,7 +438,7 @@ describe('Unread Indicators', () => { return waitFor(() => expect(isNewMessagesBadgeVisible()).toBe(false)); })); - it('Removes the new line indicator when a new message is created by the current user', () => + it('Keep showing the new line indicator when a new message is created by the current user', () => signInAndGetAppWithUnreadChat() .then(() => { // Verify we are on the LHN and that the chat shows as unread in the LHN @@ -459,7 +459,7 @@ describe('Unread Indicators', () => { .then(() => { const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); - expect(unreadIndicator).toHaveLength(0); + expect(unreadIndicator).toHaveLength(1); })); xit('Keeps the new line indicator when the user moves the App to the background', () =>