Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new marker Badge for Report unread messages #4603

Merged
merged 9 commits into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ export default {
reportActionsView: {
beFirstPersonToComment: 'Be the first person to comment',
},
reportActionsViewMarkerBadge: {
newMsg: ({count}) => `${count} new message${count > 1 ? 's' : ''}`,
},
reportTypingIndicator: {
isTyping: 'is typing...',
areTyping: 'are typing...',
Expand Down
3 changes: 3 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ export default {
reportActionsView: {
beFirstPersonToComment: 'Sé el primero en comentar',
},
reportActionsViewMarkerBadge: {
newMsg: ({count}) => `${count} mensaje${count > 1 ? 's' : ''} nuevo${count > 1 ? 's' : ''}`,
},
reportTypingIndicator: {
isTyping: 'está escribiendo...',
areTyping: 'están escribiendo...',
Expand Down
5 changes: 4 additions & 1 deletion src/pages/home/ReportScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ class ReportScreen extends React.Component {
onNavigationMenuButtonClicked={() => Navigation.navigate(ROUTES.HOME)}
/>

<View nativeID={CONST.REPORT.DROP_NATIVE_ID} style={[styles.flex1, styles.justifyContentEnd]}>
<View
nativeID={CONST.REPORT.DROP_NATIVE_ID}
style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]}
Beamanator marked this conversation as resolved.
Show resolved Hide resolved
>
<FullScreenLoadingIndicator visible={this.shouldShowLoader()} />
{!this.shouldShowLoader() && <ReportActionsView reportID={reportID} />}
{this.props.session.shouldShowComposeInput && (
Expand Down
125 changes: 125 additions & 0 deletions src/pages/home/report/MarkerBadge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, {PureComponent} from 'react';
import {Animated, Text, View} from 'react-native';
import PropTypes from 'prop-types';
import styles from '../../../styles/styles';
import Button from '../../../components/Button';
import Icon from '../../../components/Icon';
import {Close, DownArrow} from '../../../components/Icon/Expensicons';
import themeColors from '../../../styles/themes/default';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';

const MARKER_NOT_ACTIVE_TRANSLATE_Y = -30;
const MARKER_ACTIVE_TRANSLATE_Y = 10;
const propTypes = {
/** Count of new messages to show in the badge */
count: PropTypes.number,

/** Whether the marker is active */
active: PropTypes.bool,

/** Callback to be called when user closes the badge */
onClose: PropTypes.func,

/** Callback to be called when user clicks the marker */
onClick: PropTypes.func,

...withLocalizePropTypes,
};
const defaultProps = {
count: 0,
active: false,
onClose: () => {},
onClick: () => {},
};
class MarkerBadge extends PureComponent {
constructor(props) {
super(props);
this.translateY = new Animated.Value(MARKER_NOT_ACTIVE_TRANSLATE_Y);
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
}

componentDidUpdate() {
if (this.props.active && this.props.count > 0) {
this.show();
} else {
this.hide();
}
}

show() {
Animated.spring(this.translateY, {
toValue: MARKER_ACTIVE_TRANSLATE_Y,
duration: 80,
useNativeDriver: true,
}).start();
}

hide() {
Animated.spring(this.translateY, {
toValue: MARKER_NOT_ACTIVE_TRANSLATE_Y,
duration: 80,
useNativeDriver: true,
}).start();
}

render() {
return (
<View style={styles.reportMarkerBadgeWrapper}>
<Animated.View style={[
styles.reportMarkerBadge,
styles.reportMarkerBadgeTransformation(this.translateY),
]}
>
<View style={[
styles.flexRow,
styles.justifyContentBetween,
styles.alignItemsCenter,
]}
>
<Button
success
small
onPress={this.props.onClick}
ContentComponent={() => (
<View style={[styles.flexRow]}>
<Icon small src={DownArrow} fill={themeColors.textReversed} />
<Text
selectable={false}
style={[
styles.ml2,
styles.buttonSmallText,
styles.textWhite,
]}
>
{this.props.translate(
'reportActionsViewMarkerBadge.newMsg',
{count: this.props.count},
)}
</Text>
</View>
)}
shouldRemoveRightBorderRadius
/>
<Button
success
small
style={[styles.buttonDropdown]}
onPress={this.props.onClose}
shouldRemoveLeftBorderRadius
ContentComponent={() => (
<Icon small src={Close} fill={themeColors.textReversed} />
)}
/>
</View>
</Animated.View>
</View>
);
}
}

MarkerBadge.propTypes = propTypes;
MarkerBadge.defaultProps = defaultProps;
MarkerBadge.displayName = 'MarkerBadge';

export default withLocalize(MarkerBadge);
58 changes: 58 additions & 0 deletions src/pages/home/report/ReportActionsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFo
import {contextMenuRef} from './ContextMenu/ReportActionContextMenu';
import PopoverReportActionContextMenu from './ContextMenu/PopoverReportActionContextMenu';
import variables from '../../../styles/variables';
import MarkerBadge from './MarkerBadge';

const propTypes = {
/** The ID of the report actions will be created for */
Expand Down Expand Up @@ -99,11 +100,17 @@ class ReportActionsView extends React.Component {

this.state = {
isLoadingMoreChats: false,
isMarkerActive: false,
};

this.currentScrollOffset = 0;
this.updateSortedReportActions(props.reportActions);
this.updateMostRecentIOUReportActionNumber(props.reportActions);
this.keyExtractor = this.keyExtractor.bind(this);
this.trackScroll = this.trackScroll.bind(this);
this.showMarker = this.showMarker.bind(this);
this.hideMarker = this.hideMarker.bind(this);
this.toggleMarker = this.toggleMarker.bind(this);
}

componentDidMount() {
Expand Down Expand Up @@ -152,6 +159,10 @@ class ReportActionsView extends React.Component {
return true;
}

if (nextState.isMarkerActive !== this.state.isMarkerActive) {
return true;
}

if (this.props.isSmallScreenWidth !== nextProps.isSmallScreenWidth) {
return true;
}
Expand Down Expand Up @@ -189,6 +200,9 @@ class ReportActionsView extends React.Component {
if (shouldRecordMaxAction) {
updateLastReadActionID(this.props.reportID);
}

// show new MarkerBadge when there is a new Message
this.toggleMarker();
}

// We want to mark the unread comments when user resize the screen to desktop
Expand Down Expand Up @@ -346,6 +360,43 @@ class ReportActionsView extends React.Component {
updateLastReadActionID(this.props.reportID);
}

/**
* Show/hide the new MarkerBadge when user is scrolling back/forth in the history of messages.
*/
toggleMarker() {
if (this.currentScrollOffset < -200 && !this.state.isMarkerActive) {
this.showMarker();
}

if (this.currentScrollOffset > -200 && this.state.isMarkerActive) {
this.hideMarker();
}
}

/**
* Show the new MarkerBadge
*/
showMarker() {
this.setState({isMarkerActive: true});
}

/**
* Hide the new MarkerBadge
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved
*/
hideMarker() {
this.setState({isMarkerActive: false});
}

/**
* keeps track of the Scroll offset of the main messages list
*
* @param {Object} {nativeEvent}
*/
trackScroll({nativeEvent}) {
this.currentScrollOffset = -nativeEvent.contentOffset.y;
this.toggleMarker();
}

/**
* Runs when the FlatList finishes laying out
*/
Expand Down Expand Up @@ -427,6 +478,12 @@ class ReportActionsView extends React.Component {

return (
<>
<MarkerBadge
active={this.state.isMarkerActive}
count={this.props.report.unreadActionCount}
onClick={scrollToBottom}
onClose={this.hideMarker}
/>
<InvertedFlatList
ref={flatListRef}
data={this.sortedReportActions}
Expand All @@ -443,6 +500,7 @@ class ReportActionsView extends React.Component {
: null}
keyboardShouldPersistTaps="handled"
onLayout={this.recordTimeToMeasureItemLayout}
onScroll={this.trackScroll}
/>
<PopoverReportActionContextMenu ref={contextMenuRef} />
</>
Expand Down
23 changes: 22 additions & 1 deletion src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import textInputAlignSelf from './utilities/textInputAlignSelf';
import CONST from '../CONST';
import positioning from './utilities/positioning';
import codeStyles from './codeStyles';
import visibility from './utilities/visibility';

const expensiPicker = {
backgroundColor: 'transparent',
Expand Down Expand Up @@ -249,7 +250,8 @@ const styles = {
},

buttonDropdown: {
marginLeft: 1,
borderLeftWidth: 1,
borderColor: themeColors.textReversed,
Beamanator marked this conversation as resolved.
Show resolved Hide resolved
},

noRightBorderRadius: {
Expand Down Expand Up @@ -1984,6 +1986,25 @@ const styles = {
communicationsLinkHeight: {
height: 20,
},

reportMarkerBadgeWrapper: {
position: 'absolute',
left: '50%',
top: 0,
zIndex: 100,
...visibility('hidden'),
},

reportMarkerBadge: {
left: '-50%',
...visibility('visible'),
},

reportMarkerBadgeTransformation: translateY => ({
transform: [
{translateY},
],
}),
};

const baseCodeTagStyles = {
Expand Down
1 change: 1 addition & 0 deletions src/styles/utilities/visibility/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default visibility => ({visibility});
1 change: 1 addition & 0 deletions src/styles/utilities/visibility/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => ({});