diff --git a/src/components/UnreadActionIndicator.js b/src/components/UnreadActionIndicator.js
new file mode 100644
index 000000000000..83154a4cbf83
--- /dev/null
+++ b/src/components/UnreadActionIndicator.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import {Animated, View} from 'react-native';
+import PropTypes from 'prop-types';
+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 => (
+
+
+
+ NEW
+
+
+);
+
+UnreadActionIndicator.propTypes = propTypes;
+UnreadActionIndicator.displayName = 'UnreadActionIndicator';
+
+export default UnreadActionIndicator;
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js
index 506c8acff05e..92f249c25107 100644
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.js
@@ -1,10 +1,16 @@
import React from 'react';
-import {View, Keyboard, AppState} from 'react-native';
+import {
+ Animated,
+ View,
+ Keyboard,
+ AppState,
+} from 'react-native';
import PropTypes from 'prop-types';
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} from '../../../libs/actions/Report';
import ONYXKEYS from '../../../ONYXKEYS';
import ReportActionItem from './ReportActionItem';
@@ -23,6 +29,12 @@ const propTypes = {
/* 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)),
@@ -34,6 +46,9 @@ const propTypes = {
};
const defaultProps = {
+ report: {
+ unreadActionCount: 0,
+ },
reportActions: {},
session: {},
};
@@ -46,8 +61,17 @@ 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.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
+ this.shouldShowUnreadActionIndicator = true;
this.state = {
refetchNeeded: true,
@@ -145,6 +169,31 @@ class ReportActionsView extends React.Component {
this.setState({refetchNeeded});
}
+ /**
+ * 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.props.isActiveReport || !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;
+ }
+
/**
* Updates and sorts the report actions by sequence number
*/
@@ -239,12 +288,21 @@ class ReportActionsView extends React.Component {
needsLayoutCalculation,
}) {
return (
-
+
+ // Using instead of a Fragment because there is a difference between how
+ // 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 && (
+
+ )}
+
+
);
}
@@ -263,6 +321,7 @@ class ReportActionsView extends React.Component {
);
}
+ this.setUpUnreadActionIndicator();
this.updateSortedReportActions();
return (
`${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+ },
reportActions: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
canEvict: props => !props.isActiveReport,
diff --git a/src/styles/styles.js b/src/styles/styles.js
index db23694b1bc2..975179b65a02 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -948,6 +948,32 @@ const styles = {
marginLeft: 8,
},
+ unreadIndicatorContainer: {
+ position: 'absolute',
+ top: -10,
+ left: 0,
+ width: '100%',
+ height: 20,
+ paddingHorizontal: 20,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+
+ unreadIndicatorLine: {
+ height: 1,
+ backgroundColor: themeColors.unreadIndicator,
+ flexGrow: 1,
+ marginRight: 8,
+ opacity: 0.5,
+ },
+
+ unreadIndicatorText: {
+ color: themeColors.unreadIndicator,
+ fontFamily: fontFamily.GTA_BOLD,
+ fontSize: variables.fontSizeSmall,
+ fontWeight: fontWeightBold,
+ },
+
flipUpsideDown: {
transform: [{rotate: '180deg'}],
},
diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js
index 2f57fb5b72cc..ea81000323fb 100644
--- a/src/styles/themes/default.js
+++ b/src/styles/themes/default.js
@@ -30,4 +30,5 @@ export default {
pillBG: colors.gray2,
buttonDisabledBG: colors.gray2,
buttonHoveredBG: colors.gray1,
+ unreadIndicator: colors.green,
};