Skip to content

Commit d5f9ef9

Browse files
Added UnreadActionIndicator
1 parent 0a44ebc commit d5f9ef9

File tree

4 files changed

+119
-8
lines changed

4 files changed

+119
-8
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import {Animated, View} from 'react-native';
3+
import PropTypes from 'prop-types';
4+
import styles from '../styles/styles';
5+
import Text from './Text';
6+
7+
const propTypes = {
8+
// Animated opacity
9+
// eslint-disable-next-line react/forbid-prop-types
10+
animatedOpacity: PropTypes.object.isRequired,
11+
};
12+
13+
const UnreadActionIndicator = props => (
14+
<Animated.View style={[
15+
styles.unreadIndicatorContainer,
16+
{opacity: props.animatedOpacity},
17+
]}
18+
>
19+
<View style={styles.unreadIndicatorLine} />
20+
<Text style={styles.unreadIndicatorText}>
21+
NEW
22+
</Text>
23+
</Animated.View>
24+
);
25+
26+
UnreadActionIndicator.propTypes = propTypes;
27+
UnreadActionIndicator.displayName = 'UnreadActionIndicator';
28+
29+
export default UnreadActionIndicator;

src/pages/home/report/ReportActionsView.js

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import React from 'react';
2-
import {View, Keyboard, AppState} from 'react-native';
2+
import {
3+
Animated, View, Keyboard, AppState, Easing,
4+
} from 'react-native';
35
import PropTypes from 'prop-types';
46
import _ from 'underscore';
57
import lodashGet from 'lodash.get';
68
import {withOnyx} from 'react-native-onyx';
79
import Text from '../../../components/Text';
10+
import UnreadActionIndicator from '../../../components/UnreadActionIndicator';
811
import {fetchActions, updateLastReadActionID} from '../../../libs/actions/Report';
912
import ONYXKEYS from '../../../ONYXKEYS';
1013
import ReportActionItem from './ReportActionItem';
@@ -23,6 +26,11 @@ const propTypes = {
2326

2427
/* Onyx Props */
2528

29+
// List of reports to display
30+
reports: PropTypes.objectOf(PropTypes.shape({
31+
reportID: PropTypes.number,
32+
})),
33+
2634
// Array of report actions for this report
2735
reportActions: PropTypes.objectOf(PropTypes.shape(ReportActionPropTypes)),
2836

@@ -34,6 +42,7 @@ const propTypes = {
3442
};
3543

3644
const defaultProps = {
45+
reports: {},
3746
reportActions: {},
3847
session: {},
3948
};
@@ -46,8 +55,15 @@ class ReportActionsView extends React.Component {
4655
this.scrollToListBottom = this.scrollToListBottom.bind(this);
4756
this.recordMaxAction = this.recordMaxAction.bind(this);
4857
this.onVisibilityChange = this.onVisibilityChange.bind(this);
49-
this.sortedReportActions = this.updateSortedReportActions();
58+
this.setUpUnreadActionIndicator = this.setUpUnreadActionIndicator.bind(this);
59+
this.updateSortedReportActions = this.updateSortedReportActions.bind(this);
60+
61+
this.sortedReportActions = [];
5062
this.timers = [];
63+
this.unreadActionCount = 0;
64+
this.shouldShowNewActionIndicator = true;
65+
this.unreadIndicatorOpacity = new Animated.Value(1);
66+
this.unreadIndicatorTimer = null;
5167

5268
this.state = {
5369
refetchNeeded: true,
@@ -121,6 +137,7 @@ class ReportActionsView extends React.Component {
121137
this.keyboardEvent.remove();
122138
}
123139

140+
clearTimeout(this.unreadIndicatorTimer);
124141
AppState.removeEventListener('change', this.onVisibilityChange);
125142

126143
_.each(this.timers, timer => clearTimeout(timer));
@@ -145,6 +162,35 @@ class ReportActionsView extends React.Component {
145162
this.setState({refetchNeeded});
146163
}
147164

165+
/**
166+
* Checks if the unreadActionIndicator should be shown.
167+
* If it does, starts a timeout for the fading out animation and creates
168+
* a flag to not show it again if the report is still open
169+
*/
170+
setUpUnreadActionIndicator() {
171+
if (!this.props.isActiveReport || !this.shouldShowNewActionIndicator) {
172+
return;
173+
}
174+
175+
this.unreadActionCount = _.find(this.props.reports, report => (
176+
report.reportID === this.props.reportID
177+
)).unreadActionCount;
178+
179+
if (this.unreadActionCount > 0) {
180+
this.unreadIndicatorOpacity = new Animated.Value(1);
181+
this.unreadIndicatorTimer = setTimeout(() => {
182+
Animated.timing(this.unreadIndicatorOpacity, {
183+
toValue: 0,
184+
duration: 500,
185+
easing: Easing.ease,
186+
useNativeDriver: false,
187+
}).start();
188+
}, 3000);
189+
}
190+
191+
this.shouldShowNewActionIndicator = false;
192+
}
193+
148194
/**
149195
* Updates and sorts the report actions by sequence number
150196
*/
@@ -239,12 +285,17 @@ class ReportActionsView extends React.Component {
239285
needsLayoutCalculation,
240286
}) {
241287
return (
242-
<ReportActionItem
243-
action={item.action}
244-
displayAsGroup={this.isConsecutiveActionMadeByPreviousActor(index)}
245-
onLayout={onLayout}
246-
needsLayoutCalculation={needsLayoutCalculation}
247-
/>
288+
<View>
289+
{this.unreadActionCount > 0 && index === this.unreadActionCount - 1 && (
290+
<UnreadActionIndicator animatedOpacity={this.unreadIndicatorOpacity} />
291+
)}
292+
<ReportActionItem
293+
action={item.action}
294+
displayAsGroup={this.isConsecutiveActionMadeByPreviousActor(index)}
295+
onLayout={onLayout}
296+
needsLayoutCalculation={needsLayoutCalculation}
297+
/>
298+
</View>
248299
);
249300
}
250301

@@ -263,6 +314,7 @@ class ReportActionsView extends React.Component {
263314
);
264315
}
265316

317+
this.setUpUnreadActionIndicator();
266318
this.updateSortedReportActions();
267319
return (
268320
<InvertedFlatList
@@ -281,6 +333,9 @@ ReportActionsView.propTypes = propTypes;
281333
ReportActionsView.defaultProps = defaultProps;
282334

283335
export default withOnyx({
336+
reports: {
337+
key: ONYXKEYS.COLLECTION.REPORT,
338+
},
284339
reportActions: {
285340
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
286341
canEvict: props => !props.isActiveReport,

src/styles/styles.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,32 @@ const styles = {
948948
marginLeft: 8,
949949
},
950950

951+
unreadIndicatorContainer: {
952+
position: 'absolute',
953+
top: -10,
954+
left: 0,
955+
width: '100%',
956+
height: 20,
957+
paddingHorizontal: 20,
958+
flexDirection: 'row',
959+
alignItems: 'center',
960+
},
961+
962+
unreadIndicatorLine: {
963+
height: 1,
964+
backgroundColor: themeColors.unreadIndicator,
965+
flexGrow: 1,
966+
marginRight: 8,
967+
opacity: 0.5,
968+
},
969+
970+
unreadIndicatorText: {
971+
color: themeColors.unreadIndicator,
972+
fontFamily: fontFamily.GTA_BOLD,
973+
fontSize: variables.fontSizeSmall,
974+
fontWeight: fontWeightBold,
975+
},
976+
951977
flipUpsideDown: {
952978
transform: [{rotate: '180deg'}],
953979
},

src/styles/themes/default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ export default {
3030
pillBG: colors.gray2,
3131
buttonDisabledBG: colors.gray2,
3232
buttonHoveredBG: colors.gray1,
33+
unreadIndicator: colors.green,
3334
};

0 commit comments

Comments
 (0)