Skip to content

Commit

Permalink
Merge pull request #3883 from parasharrajat/timezone
Browse files Browse the repository at this point in the history
Feature: Show local time for User in 1:1 chat
  • Loading branch information
Beamanator authored Jul 8, 2021
2 parents 2bda7d6 + fe0587b commit ac0f038
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 29 deletions.
1 change: 1 addition & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export default {
blockedFromConcierge: 'Communication is barred',
youAppearToBeOffline: 'You appear to be offline.',
fileUploadFailed: 'Upload Failed. File is not supported.',
localTime: ({user, time}) => `It's ${time} for ${user}`,
},
reportActionContextMenu: {
copyToClipboard: 'Copy to Clipboard',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export default {
writeSomething: 'Escribe algo...',
blockedFromConcierge: 'Comunicación no permitida',
youAppearToBeOffline: 'Parece que estás desconectado.',
localTime: ({user, time}) => `Son las ${time} para ${user}`,
},
reportActionContextMenu: {
copyToClipboard: 'Copiar al Portapapeles',
Expand Down
83 changes: 83 additions & 0 deletions src/pages/home/report/ParticipantLocalTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import {
View,
} from 'react-native';
import lodashGet from 'lodash/get';
import moment from 'moment';
import Str from 'expensify-common/lib/str';
import styles from '../../../styles/styles';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import {participantPropTypes} from '../sidebar/optionPropTypes';
import ExpensiText from '../../../components/Text';
import Timers from '../../../libs/Timers';

const propTypes = {
/** Personal details of the participant */
participant: participantPropTypes.isRequired,

...withLocalizePropTypes,
};

class ParticipantLocalTime extends React.Component {
constructor(props) {
super(props);
this.getParticipantLocalTime = this.getParticipantLocalTime.bind(this);
this.state = {
localTime: this.getParticipantLocalTime(),
};
}

componentDidMount() {
this.timer = Timers.register(setInterval(() => {
this.setState({
localTime: this.getParticipantLocalTime(),
});
}, 1000));
}

componentWillUnmount() {
clearInterval(this.timer);
clearInterval(this.readyTimer);
}

getParticipantLocalTime() {
const reportRecipientTimezone = lodashGet(this.props.participant, 'timezone', {});
return moment().tz(reportRecipientTimezone.selected).format('LT');
}


render() {
// Moment.format does not return AM or PM values immediately.
// So we have to wait until we are ready before showing the time to the user
const isReportRecipientLocalTimeReady = this.state.localTime.toString().match(/(A|P)M/ig);
const reportRecipientDisplayName = this.props.participant.firstName
|| (Str.isSMSLogin(this.props.participant.login)
? this.props.toLocalPhone(this.props.participant.displayName)
: this.props.participant.displayName);

return (
isReportRecipientLocalTimeReady ? (
<View style={[styles.chatItemComposeSecondaryRow]}>
<ExpensiText style={[
styles.chatItemComposeSecondaryRowSubText,
styles.chatItemComposeSecondaryRowOffset,
]}
>
{this.props.translate(
'reportActionCompose.localTime',
{
user: reportRecipientDisplayName,
time: this.state.localTime,
},
)}
</ExpensiText>
</View>
)
: <View style={[styles.chatItemComposeSecondaryRow]} />
);
}
}

ParticipantLocalTime.propTypes = propTypes;

export default withLocalize(ParticipantLocalTime);
36 changes: 33 additions & 3 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ import * as User from '../../../libs/actions/User';
import ReportActionPropTypes from './ReportActionPropTypes';
import {canEditReportAction} from '../../../libs/reportUtils';
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';
import {participantPropTypes} from '../sidebar/optionPropTypes';
import currentUserPersonalDetailsPropsTypes from '../../settings/Profile/currentUserPersonalDetailsPropsTypes';
import ParticipantLocalTime from './ParticipantLocalTime';

const propTypes = {
/** Beta features list */
Expand All @@ -73,6 +76,12 @@ const propTypes = {
isVisible: PropTypes.bool,
}),

/** The personal details of the person who is logged in */
myPersonalDetails: PropTypes.shape(currentUserPersonalDetailsPropsTypes).isRequired,

/** Personal details of all the users */
personalDetails: PropTypes.objectOf(participantPropTypes).isRequired,

/** The report currently being looked at */
report: PropTypes.shape({

Expand Down Expand Up @@ -392,8 +401,17 @@ class ReportActionCompose extends React.Component {

render() {
// eslint-disable-next-line no-unused-vars
const hasMultipleParticipants = lodashGet(this.props.report, 'participants.length') > 1;
const hasConciergeParticipant = _.contains(this.props.report.participants, CONST.EMAIL.CONCIERGE);
const reportParticipants = lodashGet(this.props.report, 'participants', []);
const hasMultipleParticipants = reportParticipants.length > 1;
const hasConciergeParticipant = _.contains(reportParticipants, CONST.EMAIL.CONCIERGE);
const reportRecipient = this.props.personalDetails[reportParticipants[0]];
const currentUserTimezone = lodashGet(this.props.myPersonalDetails, 'timezone', {});
const reportRecipientTimezone = lodashGet(reportRecipient, 'timezone', {});
const shouldShowReportRecipientLocalTime = !hasConciergeParticipant
&& !hasMultipleParticipants
&& reportRecipient
&& reportRecipientTimezone
&& currentUserTimezone.selected !== reportRecipientTimezone.selected;

// Prevents focusing and showing the keyboard while the drawer is covering the chat.
const isComposeDisabled = this.props.isDrawerOpen && this.props.isSmallScreenWidth;
Expand All @@ -409,7 +427,13 @@ class ReportActionCompose extends React.Component {
: this.props.translate('reportActionCompose.writeSomething');

return (
<View style={[styles.chatItemCompose]}>
<View style={[
styles.chatItemCompose,
shouldShowReportRecipientLocalTime && styles.chatItemComposeWithFirstRow,
]}
>
{shouldShowReportRecipientLocalTime
&& <ParticipantLocalTime participant={reportRecipient} />}
<View style={[
(this.state.isFocused || this.state.isDraggingOver)
? styles.chatItemComposeBoxFocusedColor
Expand Down Expand Up @@ -634,6 +658,12 @@ export default compose(
network: {
key: ONYXKEYS.NETWORK,
},
myPersonalDetails: {
key: ONYXKEYS.MY_PERSONAL_DETAILS,
},
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS,
},
reportActions: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
canEvict: false,
Expand Down
3 changes: 3 additions & 0 deletions src/pages/home/sidebar/optionPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ const participantPropTypes = PropTypes.shape({

// Avatar url of participant
avatar: PropTypes.string,

/** First Name of the participant */
firstName: PropTypes.string,
});

const optionPropTypes = PropTypes.shape({
Expand Down
28 changes: 2 additions & 26 deletions src/pages/settings/Profile/ProfilePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,13 @@ import FixedFooter from '../../../components/FixedFooter';
import Growl from '../../../libs/Growl';
import FullNameInputRow from '../../../components/FullNameInputRow';
import CheckboxWithLabel from '../../../components/CheckboxWithLabel';
import currentUserPersonalDetailsPropsTypes from './currentUserPersonalDetailsPropsTypes';

const propTypes = {
/* Onyx Props */

/** The personal details of the person who is logged in */
myPersonalDetails: PropTypes.shape({
/** Email/Phone login of the current user from their personal details */
login: PropTypes.string,

/** Display first name of the current user from their personal details */
firstName: PropTypes.string,

/** Display last name of the current user from their personal details */
lastName: PropTypes.string,

/** Avatar URL of the current user from their personal details */
avatar: PropTypes.string,

/** Pronouns of the current user from their personal details */
pronouns: PropTypes.string,

/** Timezone of the current user from their personal details */
timezone: PropTypes.shape({

/** Value of selected timezone */
selected: PropTypes.string,

/** Whether timezone is automatically set */
automatic: PropTypes.bool,
}),
}),
myPersonalDetails: PropTypes.shape(currentUserPersonalDetailsPropsTypes),

/** The details about the user that is signed in */
user: PropTypes.shape({
Expand Down
31 changes: 31 additions & 0 deletions src/pages/settings/Profile/currentUserPersonalDetailsPropsTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import PropTypes from 'prop-types';

/** The personal details of the person who is logged in */
const currentUserPersonalDetailsPropsTypes = {
/** Email/Phone login of the current user from their personal details */
login: PropTypes.string,

/** Display first name of the current user from their personal details */
firstName: PropTypes.string,

/** Display last name of the current user from their personal details */
lastName: PropTypes.string,

/** Avatar URL of the current user from their personal details */
avatar: PropTypes.string,

/** Pronouns of the current user from their personal details */
pronouns: PropTypes.string,

/** Timezone of the current user from their personal details */
timezone: PropTypes.shape({

/** Value of selected timezone */
selected: PropTypes.string,

/** Whether timezone is automatically set */
automatic: PropTypes.bool,
}),
};

export default currentUserPersonalDetailsPropsTypes;
4 changes: 4 additions & 0 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,10 @@ const styles = {
display: 'flex',
},

chatItemComposeWithFirstRow: {
minHeight: 85,
},

chatItemComposeBoxColor: {
borderColor: themeColors.border,
},
Expand Down

0 comments on commit ac0f038

Please sign in to comment.