diff --git a/src/components/UnreadActionIndicator.js b/src/components/UnreadActionIndicator.js
index 83154a4cbf83..d2ffcc232c2a 100644
--- a/src/components/UnreadActionIndicator.js
+++ b/src/components/UnreadActionIndicator.js
@@ -1,29 +1,17 @@
import React from 'react';
-import {Animated, View} from 'react-native';
-import PropTypes from 'prop-types';
+import {View} from 'react-native';
import styles from '../styles/styles';
import Text from './Text';
-const propTypes = {
- // Animated opacity
- // eslint-disable-next-line react/forbid-prop-types
- animatedOpacity: PropTypes.object.isRequired,
-};
-
-const UnreadActionIndicator = props => (
-
+const UnreadActionIndicator = () => (
+
NEW
-
+
);
-UnreadActionIndicator.propTypes = propTypes;
UnreadActionIndicator.displayName = 'UnreadActionIndicator';
export default UnreadActionIndicator;
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 11988ae99b21..79350d2260eb 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -204,7 +204,7 @@ function setLocalLastRead(reportID, sequenceNumber) {
// Update the report optimistically
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
- unreadActionCount: 0,
+ unreadActionCount: Math.max(reportMaxSequenceNumbers[reportID] - sequenceNumber, 0),
lastVisitedTimestamp: Date.now(),
});
}
@@ -669,16 +669,18 @@ function addAction(reportID, text, file) {
*
* @param {Number} reportID
* @param {Number} sequenceNumber
+ * @param {Boolean} ignoreOrder If set to true, we will not enforce the latest read action to be at the bottom of the
+ * chat.
*/
-function updateLastReadActionID(reportID, sequenceNumber) {
+function updateLastReadActionID(reportID, sequenceNumber, ignoreOrder = false) {
const currentMaxSequenceNumber = reportMaxSequenceNumbers[reportID];
- if (sequenceNumber < currentMaxSequenceNumber) {
+ if (!ignoreOrder && sequenceNumber < currentMaxSequenceNumber) {
return;
}
setLocalLastRead(reportID, sequenceNumber);
- // Mark the report as not having any unread items
+ // Mark the unread items in the report
API.Report_UpdateLastRead({
accountID: currentUserAccountID,
reportID,
diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js
index 80156f9443e3..1b44290a51ab 100644
--- a/src/pages/home/report/ReportActionContextMenu.js
+++ b/src/pages/home/report/ReportActionContextMenu.js
@@ -7,41 +7,6 @@ import {
import getReportActionContextMenuStyles from '../../../styles/getReportActionContextMenuStyles';
import ReportActionContextMenuItem from './ReportActionContextMenuItem';
-/**
- * A list of all the context actions in this menu.
- */
-const CONTEXT_ACTIONS = [
- // Copy to clipboard
- {
- text: 'Copy to Clipboard',
- icon: Clipboard,
- },
-
- // Copy chat link
- {
- text: 'Copy Link',
- icon: LinkCopy,
- },
-
- // Mark as Unread
- {
- text: 'Mark as Unread',
- icon: Mail,
- },
-
- // Edit Comment
- {
- text: 'Edit Comment',
- icon: Pencil,
- },
-
- // Delete Comment
- {
- text: 'Delete Comment',
- icon: Trashcan,
- },
-];
-
const propTypes = {
// The ID of the report this report action is attached to.
// eslint-disable-next-line react/no-unused-prop-types
@@ -57,14 +22,57 @@ const propTypes = {
// Controls the visibility of this component.
isVisible: PropTypes.bool,
+
+ // Function to trigger when we try to mark a message as unread
+ onMarkAsUnread: PropTypes.func,
};
const defaultProps = {
isMini: false,
isVisible: false,
+ onMarkAsUnread: () => {},
};
const ReportActionContextMenu = (props) => {
+ /**
+ * A list of all the context actions in this menu.
+ */
+ const CONTEXT_ACTIONS = [
+ // Copy to clipboard
+ {
+ text: 'Copy to Clipboard',
+ icon: Clipboard,
+ onPress: () => {},
+ },
+
+ // Copy chat link
+ {
+ text: 'Copy Link',
+ icon: LinkCopy,
+ onPress: () => {},
+ },
+
+ // Mark as Unread
+ {
+ text: 'Mark as Unread',
+ icon: Mail,
+ onPress: props.onMarkAsUnread,
+ },
+
+ // Edit Comment
+ {
+ text: 'Edit Comment',
+ icon: Pencil,
+ onPress: () => {},
+ },
+
+ // Delete Comment
+ {
+ text: 'Delete Comment',
+ icon: Trashcan,
+ onPress: () => {},
+ },
+ ];
const wrapperStyle = getReportActionContextMenuStyles(props.isMini);
return props.isVisible && (
@@ -74,6 +82,7 @@ const ReportActionContextMenu = (props) => {
text={contextAction.text}
isMini={props.isMini}
key={contextAction.text}
+ onPress={contextAction.onPress}
/>
))}
diff --git a/src/pages/home/report/ReportActionContextMenuItem.js b/src/pages/home/report/ReportActionContextMenuItem.js
index 6b60ce74d9f5..a57715e6d0ee 100644
--- a/src/pages/home/report/ReportActionContextMenuItem.js
+++ b/src/pages/home/report/ReportActionContextMenuItem.js
@@ -27,13 +27,23 @@ function getButtonState(isHovered = false, isPressed = false) {
}
const propTypes = {
+
+ // Icon to display in the menu
icon: PropTypes.elementType.isRequired,
+
+ // Text for the action
text: PropTypes.string.isRequired,
+
+ // If true, we are displaying the mini hover-menu
isMini: PropTypes.bool,
+
+ // Function to trigger when the action is pressed
+ onPress: PropTypes.func,
};
const defaultProps = {
isMini: false,
+ onPress: () => {},
};
const ReportActionContextMenuItem = (props) => {
@@ -42,7 +52,10 @@ const ReportActionContextMenuItem = (props) => {
props.isMini
? (
- getButtonStyle(getButtonState(hovered, pressed))}>
+ getButtonStyle(getButtonState(hovered, pressed))}
+ onPress={props.onPress}
+ >
{({hovered, pressed}) => (
{
) : (
- getButtonStyle(getButtonState(hovered, pressed))}>
+ getButtonStyle(getButtonState(hovered, pressed))}
+ onPress={props.onPress}
+ >
{({hovered, pressed}) => (
<>
0
+ && ((this.props.index === this.props.report.unreadActionCount - 1)
+ !== (nextProps.index === nextProps.report.unreadActionCount - 1));
+
return this.state.isPopoverVisible !== nextState.isPopoverVisible
|| this.props.displayAsGroup !== nextProps.displayAsGroup
+ || hasNewDisplayChanged
|| !_.isEqual(this.props.action, nextProps.action);
}
@@ -102,11 +123,14 @@ class ReportActionItem extends Component {
}
render() {
+ const displayNewIndicator = this.props.report.unreadActionCount > 0
+ && this.props.index === this.props.report.unreadActionCount - 1;
return (
{hovered => (
+ {displayNewIndicator && }
{!this.props.displayAsGroup
?
@@ -121,6 +145,7 @@ class ReportActionItem extends Component {
&& this.isInReportActionContextMenuBeta()
&& !this.state.isPopoverVisible
}
+ onMarkAsUnread={this.props.onMarkAsUnread}
isMini
/>
@@ -141,6 +166,7 @@ class ReportActionItem extends Component {
isVisible={this.state.isPopoverVisible}
reportID={this.props.reportID}
reportActionID={this.props.action.sequenceNumber}
+ onMarkAsUnread={this.props.onMarkAsUnread}
/>
@@ -158,4 +184,7 @@ export default withOnyx({
betas: {
key: ONYXKEYS.BETAS,
},
+ report: {
+ key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ },
})(ReportActionItem);
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js
index 4d626ebba79b..41ba98a496bb 100644
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.js
@@ -1,6 +1,5 @@
import React from 'react';
import {
- Animated,
View,
Keyboard,
AppState,
@@ -11,7 +10,6 @@ import _ from 'underscore';
import lodashGet from 'lodash.get';
import {withOnyx} from 'react-native-onyx';
import Text from '../../../components/Text';
-import UnreadActionIndicator from '../../../components/UnreadActionIndicator';
import {
fetchActions,
updateLastReadActionID,
@@ -34,13 +32,6 @@ const propTypes = {
reportID: PropTypes.number.isRequired,
/* Onyx Props */
-
- // The report currently being looked at
- report: PropTypes.shape({
- // Number of actions unread
- unreadActionCount: PropTypes.number,
- }),
-
// Array of report actions for this report
reportActions: PropTypes.objectOf(PropTypes.shape(ReportActionPropTypes)),
@@ -52,9 +43,6 @@ const propTypes = {
};
const defaultProps = {
- report: {
- unreadActionCount: 0,
- },
reportActions: {},
session: {},
};
@@ -68,13 +56,10 @@ class ReportActionsView extends React.Component {
this.scrollToListBottom = this.scrollToListBottom.bind(this);
this.recordMaxAction = this.recordMaxAction.bind(this);
this.onVisibilityChange = this.onVisibilityChange.bind(this);
+ this.sortedReportActions = this.updateSortedReportActions();
this.loadMoreChats = this.loadMoreChats.bind(this);
this.sortedReportActions = [];
this.timers = [];
- this.unreadIndicatorOpacity = new Animated.Value(1);
-
- // Helper variable that keeps track of the unread action count before it updates to zero
- this.unreadActionCount = 0;
// Helper variable that prevents the unread indicator to show up for new messages
// received while the report is still active
@@ -113,6 +98,7 @@ class ReportActionsView extends React.Component {
// We have switched to a new report
if (prevProps.reportID !== this.props.reportID) {
this.reset(prevProps.reportID);
+ this.shouldShowUnreadActionIndicator = true;
return;
}
@@ -155,31 +141,6 @@ class ReportActionsView extends React.Component {
}
}
- /**
- * Checks if the unreadActionIndicator should be shown.
- * If it does, starts a timeout for the fading out animation and creates
- * a flag to not show it again if the report is still open
- */
- setUpUnreadActionIndicator() {
- if (!this.shouldShowUnreadActionIndicator) {
- return;
- }
-
- this.unreadActionCount = this.props.report.unreadActionCount;
-
- if (this.unreadActionCount > 0) {
- this.unreadIndicatorOpacity = new Animated.Value(1);
- this.timers.push(setTimeout(() => {
- Animated.timing(this.unreadIndicatorOpacity, {
- toValue: 0,
- useNativeDriver: false,
- }).start();
- }, 3000));
- }
-
- this.shouldShowUnreadActionIndicator = false;
- }
-
/**
* Actions to run when the report has been updated
* @param {Number} oldReportID
@@ -274,7 +235,6 @@ class ReportActionsView extends React.Component {
.pluck('sequenceNumber')
.max()
.value();
-
updateLastReadActionID(this.props.reportID, maxVisibleSequenceNumber);
}
@@ -335,15 +295,15 @@ class ReportActionsView extends React.Component {
// are implemented on native and web/desktop which leads to
// the unread indicator on native to render below the message instead of above it.
- {this.unreadActionCount > 0 && index === this.unreadActionCount - 1 && (
-
- )}
updateLastReadActionID(this.props.reportID,
+ item.action.sequenceNumber - 1, true)}
/>
);
@@ -364,7 +324,7 @@ class ReportActionsView extends React.Component {
);
}
- this.setUpUnreadActionIndicator();
+ this.shouldShowUnreadActionIndicator = false;
this.updateSortedReportActions();
return (
`${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
- },
reportActions: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
canEvict: false,
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 1f44256c7858..c173abf5f33b 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -772,7 +772,6 @@ const styles = {
left: 0,
top: 0,
bottom: 0,
- zIndex: 2,
},
sidebarVisible: {
@@ -951,6 +950,7 @@ const styles = {
...positioning.tn4,
...positioning.r4,
position: 'absolute',
+ zIndex: variables.zIndexTop,
},
reportActionContextMenuText: {
@@ -1038,13 +1038,14 @@ const styles = {
unreadIndicatorContainer: {
position: 'absolute',
- top: -10,
+ top: -5,
left: 0,
width: '100%',
- height: 20,
+ height: 10,
paddingHorizontal: 20,
flexDirection: 'row',
alignItems: 'center',
+ zIndex: variables.zIndexMiddle,
},
unreadIndicatorLine: {
diff --git a/src/styles/variables.js b/src/styles/variables.js
index d4b5ecc592a3..1f0a5fe00f44 100644
--- a/src/styles/variables.js
+++ b/src/styles/variables.js
@@ -19,4 +19,6 @@ export default {
safeInsertPercentage: 0.7,
sideBarWidth: 375,
pdfPageMaxWidth: 992,
+ zIndexMiddle: 50,
+ zIndexTop: 999,
};