From edbd04ae8596cc7713723446a9d1666ef13e3aa7 Mon Sep 17 00:00:00 2001 From: Dmytro Klymenko Date: Tue, 24 Aug 2021 10:18:00 +0300 Subject: [PATCH 1/4] #4517 update timezone on user activity --- src/libs/DateUtils.js | 30 +++++++++++++------ .../Navigation/AppNavigator/AuthScreens.js | 26 ++-------------- src/pages/home/report/ReportActionCompose.js | 3 ++ src/pages/settings/InitialPage.js | 6 +++- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index b2107c82131..1ffe14c5e9e 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -8,19 +8,13 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import {translate} from './translate'; +import * as PersonalDetails from './actions/PersonalDetails'; let timezone; Onyx.connect({ key: ONYXKEYS.MY_PERSONAL_DETAILS, callback: (val) => { - timezone = val ? val.timezone : CONST.DEFAULT_TIME_ZONE.selected; - - // Make sure that if we have a timezone in object format that we're getting the selected timezone name - // Older timezone formats only include the timezone name, but the newer format also included whether or - // not the timezone was selected automatically - if (_.isObject(timezone)) { - timezone = val.timezone.selected; - } + timezone = val ? val.timezone : CONST.DEFAULT_TIME_ZONE; }, }); @@ -37,7 +31,7 @@ Onyx.connect({ */ function getLocalMomentFromTimestamp(locale, timestamp) { moment.locale(locale); - return moment.unix(timestamp).tz(timezone); + return moment.unix(timestamp).tz(timezone.selected); } /** @@ -115,6 +109,22 @@ function startCurrentDateUpdater() { }); } +/* + * Updates user's timezone, if their timezone is set to automatic and + * is different from current timezone + */ +function updateTimezone() { + const currentTimezone = moment.tz.guess(true); + if (timezone.automatic && timezone.selected !== currentTimezone) { + PersonalDetails.setPersonalDetails({timezone: {...timezone, selected: currentTimezone}}); + } +} + +/* + * Returns a version of updateTimezone function throttled by 5 minutes + */ +const throttledUpdateTimezone = _.throttle(() => updateTimezone(), 1000 * 60 * 5); + /** * @namespace DateUtils */ @@ -122,6 +132,8 @@ const DateUtils = { timestampToRelative, timestampToDateTime, startCurrentDateUpdater, + updateTimezone, + throttledUpdateTimezone, }; export default DateUtils; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 618b99be40b..848963e4da2 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -1,9 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Onyx, {withOnyx} from 'react-native-onyx'; -import moment from 'moment'; -import _ from 'underscore'; -import lodashGet from 'lodash/get'; +import {withOnyx} from 'react-native-onyx'; import styles, {getNavigationModalCardStyle} from '../../../styles/styles'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import CONST from '../../../CONST'; @@ -67,25 +64,7 @@ import WorkspaceSettingsDrawerNavigator from './WorkspaceSettingsDrawerNavigator import spacing from '../../../styles/utilities/spacing'; import CardOverlay from '../../../components/CardOverlay'; import defaultScreenOptions from './defaultScreenOptions'; - -Onyx.connect({ - key: ONYXKEYS.MY_PERSONAL_DETAILS, - callback: (val) => { - if (!val) { - return; - } - - const timezone = lodashGet(val, 'timezone', {}); - const currentTimezone = moment.tz.guess(true); - - // If the current timezone is different than the user's timezone, and their timezone is set to automatic - // then update their timezone. - if (_.isObject(timezone) && timezone.automatic && timezone.selected !== currentTimezone) { - timezone.selected = currentTimezone; - PersonalDetails.setPersonalDetails({timezone}); - } - }, -}); +import DateUtils from '../../DateUtils'; const RootStack = createCustomModalStackNavigator(); @@ -145,6 +124,7 @@ class AuthScreens extends React.Component { } componentDidMount() { + DateUtils.updateTimezone(); NetworkConnection.listenForReconnect(); PusherConnectionManager.init(); Pusher.init({ diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 65eae4426d6..0b2c5130334 100755 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -57,6 +57,7 @@ import {participantPropTypes} from '../sidebar/optionPropTypes'; import currentUserPersonalDetailsPropsTypes from '../../settings/Profile/currentUserPersonalDetailsPropsTypes'; import ParticipantLocalTime from './ParticipantLocalTime'; import {withNetwork, withPersonalDetails} from '../../../components/OnyxProvider'; +import DateUtils from '../../../libs/DateUtils'; const propTypes = { /** Beta features list */ @@ -426,6 +427,8 @@ class ReportActionCompose extends React.Component { return; } + DateUtils.throttledUpdateTimezone(); + this.props.onSubmit(trimmedComment); this.updateComment(''); this.setTextInputShouldClear(true); diff --git a/src/pages/settings/InitialPage.js b/src/pages/settings/InitialPage.js index 8bfa5859618..f806b55d069 100755 --- a/src/pages/settings/InitialPage.js +++ b/src/pages/settings/InitialPage.js @@ -27,6 +27,7 @@ import ROUTES from '../../ROUTES'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import compose from '../../libs/compose'; import CONST from '../../CONST'; +import DateUtils from '../../libs/DateUtils'; const propTypes = { /* Onyx Props */ @@ -90,7 +91,10 @@ const defaultMenuItems = [ { translationKey: 'common.profile', icon: Profile, - action: () => { Navigation.navigate(ROUTES.SETTINGS_PROFILE); }, + action: () => { + DateUtils.updateTimezone(); + Navigation.navigate(ROUTES.SETTINGS_PROFILE); + }, }, { translationKey: 'common.preferences', From 81e979cb6b87020e0c0f74f6977d8c54fae078f4 Mon Sep 17 00:00:00 2001 From: Dmytro Klymenko Date: Tue, 24 Aug 2021 15:10:37 +0300 Subject: [PATCH 2/4] add timeout to updateTimezone call on app start --- src/libs/Navigation/AppNavigator/AuthScreens.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 848963e4da2..9f4be93518c 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -124,7 +124,6 @@ class AuthScreens extends React.Component { } componentDidMount() { - DateUtils.updateTimezone(); NetworkConnection.listenForReconnect(); PusherConnectionManager.init(); Pusher.init({ @@ -181,6 +180,9 @@ class AuthScreens extends React.Component { this.unsubscribeGroupShortcut = KeyboardShortcut.subscribe('K', () => { Navigation.navigate(ROUTES.NEW_GROUP); }, groupShortcutModifiers, true); + setTimeout(() => { + DateUtils.updateTimezone(); + }, 1000); } shouldComponentUpdate(nextProps) { From 7f948fe876d2d334de04f52fd4fd67984635c714 Mon Sep 17 00:00:00 2001 From: Dmytro Klymenko Date: Tue, 24 Aug 2021 15:44:30 +0300 Subject: [PATCH 3/4] #4517 avoid init check race conditions --- .../Navigation/AppNavigator/AuthScreens.js | 4 - src/libs/actions/PersonalDetails.js | 73 +++++++++++-------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 9f4be93518c..2e368fd3750 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -64,7 +64,6 @@ import WorkspaceSettingsDrawerNavigator from './WorkspaceSettingsDrawerNavigator import spacing from '../../../styles/utilities/spacing'; import CardOverlay from '../../../components/CardOverlay'; import defaultScreenOptions from './defaultScreenOptions'; -import DateUtils from '../../DateUtils'; const RootStack = createCustomModalStackNavigator(); @@ -180,9 +179,6 @@ class AuthScreens extends React.Component { this.unsubscribeGroupShortcut = KeyboardShortcut.subscribe('K', () => { Navigation.navigate(ROUTES.NEW_GROUP); }, groupShortcutModifiers, true); - setTimeout(() => { - DateUtils.updateTimezone(); - }, 1000); } shouldComponentUpdate(nextProps) { diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js index 269e021b95a..ed1d25ac837 100644 --- a/src/libs/actions/PersonalDetails.js +++ b/src/libs/actions/PersonalDetails.js @@ -3,6 +3,7 @@ import lodashGet from 'lodash/get'; import lodashMerge from 'lodash/merge'; import Onyx from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; +import moment from 'moment'; import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import NetworkConnection from '../NetworkConnection'; @@ -93,6 +94,38 @@ function formatPersonalDetails(personalDetailsList) { }, {}); } +/** + * Merges partial details object into the local store. + * + * @param {Object} details + * @private + */ +function mergeLocalPersonalDetails(details) { + // We are merging the partial details provided to this method with the existing details we have for the user so + // that we don't overwrite any values that may exist in storage. + const mergedDetails = lodashMerge(personalDetails[currentUserEmail], details); + + // displayName is a generated field so we'll use the firstName and lastName + login to update it. + mergedDetails.displayName = getDisplayName(currentUserEmail, mergedDetails); + + // Update the associated Onyx keys + Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, mergedDetails); + Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[currentUserEmail]: mergedDetails}); +} + +/** + * Sets the personal details object for the current user + * + * @param {Object} details + */ +function setPersonalDetails(details) { + API.PersonalDetails_Update({details: JSON.stringify(details)}); + if (details.timezone) { + NameValuePair.set(CONST.NVP.TIMEZONE, details.timezone); + } + mergeLocalPersonalDetails(details); +} + /** * Get the personal details for our organization * @returns {Promise} @@ -120,6 +153,14 @@ function fetchPersonalDetails() { myPersonalDetails.firstName = lodashGet(data.personalDetailsList, [currentUserEmail, 'firstName'], ''); myPersonalDetails.lastName = lodashGet(data.personalDetailsList, [currentUserEmail, 'lastName'], ''); + + // Update user's timezone, if their timezone is set to automatic and + // is different from current timezone + const currentTimezone = moment.tz.guess(true); + if (myPersonalDetails.timezone.automatic && myPersonalDetails.timezone.selected !== currentTimezone) { + myPersonalDetails.timezone.selected = currentTimezone; + } + // Set my personal details so they can be easily accessed and subscribed to on their own key Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, myPersonalDetails); }) @@ -187,38 +228,6 @@ function getFromReportParticipants(reports) { }); } -/** - * Merges partial details object into the local store. - * - * @param {Object} details - * @private - */ -function mergeLocalPersonalDetails(details) { - // We are merging the partial details provided to this method with the existing details we have for the user so - // that we don't overwrite any values that may exist in storage. - const mergedDetails = lodashMerge(personalDetails[currentUserEmail], details); - - // displayName is a generated field so we'll use the firstName and lastName + login to update it. - mergedDetails.displayName = getDisplayName(currentUserEmail, mergedDetails); - - // Update the associated Onyx keys - Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, mergedDetails); - Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[currentUserEmail]: mergedDetails}); -} - -/** - * Sets the personal details object for the current user - * - * @param {Object} details - */ -function setPersonalDetails(details) { - API.PersonalDetails_Update({details: JSON.stringify(details)}); - if (details.timezone) { - NameValuePair.set(CONST.NVP.TIMEZONE, details.timezone); - } - mergeLocalPersonalDetails(details); -} - /** * Sets the onyx with the currency list from the network * @returns {Object} From b2944dddd365456afd20b6330137049946e24058 Mon Sep 17 00:00:00 2001 From: Dmytro Klymenko Date: Tue, 24 Aug 2021 16:01:49 +0300 Subject: [PATCH 4/4] revert init timezone check --- .../Navigation/AppNavigator/AuthScreens.js | 24 +++++- src/libs/actions/PersonalDetails.js | 73 ++++++++----------- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 2e368fd3750..618b99be40b 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -1,6 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; +import Onyx, {withOnyx} from 'react-native-onyx'; +import moment from 'moment'; +import _ from 'underscore'; +import lodashGet from 'lodash/get'; import styles, {getNavigationModalCardStyle} from '../../../styles/styles'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import CONST from '../../../CONST'; @@ -65,6 +68,25 @@ import spacing from '../../../styles/utilities/spacing'; import CardOverlay from '../../../components/CardOverlay'; import defaultScreenOptions from './defaultScreenOptions'; +Onyx.connect({ + key: ONYXKEYS.MY_PERSONAL_DETAILS, + callback: (val) => { + if (!val) { + return; + } + + const timezone = lodashGet(val, 'timezone', {}); + const currentTimezone = moment.tz.guess(true); + + // If the current timezone is different than the user's timezone, and their timezone is set to automatic + // then update their timezone. + if (_.isObject(timezone) && timezone.automatic && timezone.selected !== currentTimezone) { + timezone.selected = currentTimezone; + PersonalDetails.setPersonalDetails({timezone}); + } + }, +}); + const RootStack = createCustomModalStackNavigator(); // We want to delay the re-rendering for components(e.g. ReportActionCompose) diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js index ed1d25ac837..269e021b95a 100644 --- a/src/libs/actions/PersonalDetails.js +++ b/src/libs/actions/PersonalDetails.js @@ -3,7 +3,6 @@ import lodashGet from 'lodash/get'; import lodashMerge from 'lodash/merge'; import Onyx from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; -import moment from 'moment'; import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import NetworkConnection from '../NetworkConnection'; @@ -94,38 +93,6 @@ function formatPersonalDetails(personalDetailsList) { }, {}); } -/** - * Merges partial details object into the local store. - * - * @param {Object} details - * @private - */ -function mergeLocalPersonalDetails(details) { - // We are merging the partial details provided to this method with the existing details we have for the user so - // that we don't overwrite any values that may exist in storage. - const mergedDetails = lodashMerge(personalDetails[currentUserEmail], details); - - // displayName is a generated field so we'll use the firstName and lastName + login to update it. - mergedDetails.displayName = getDisplayName(currentUserEmail, mergedDetails); - - // Update the associated Onyx keys - Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, mergedDetails); - Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[currentUserEmail]: mergedDetails}); -} - -/** - * Sets the personal details object for the current user - * - * @param {Object} details - */ -function setPersonalDetails(details) { - API.PersonalDetails_Update({details: JSON.stringify(details)}); - if (details.timezone) { - NameValuePair.set(CONST.NVP.TIMEZONE, details.timezone); - } - mergeLocalPersonalDetails(details); -} - /** * Get the personal details for our organization * @returns {Promise} @@ -153,14 +120,6 @@ function fetchPersonalDetails() { myPersonalDetails.firstName = lodashGet(data.personalDetailsList, [currentUserEmail, 'firstName'], ''); myPersonalDetails.lastName = lodashGet(data.personalDetailsList, [currentUserEmail, 'lastName'], ''); - - // Update user's timezone, if their timezone is set to automatic and - // is different from current timezone - const currentTimezone = moment.tz.guess(true); - if (myPersonalDetails.timezone.automatic && myPersonalDetails.timezone.selected !== currentTimezone) { - myPersonalDetails.timezone.selected = currentTimezone; - } - // Set my personal details so they can be easily accessed and subscribed to on their own key Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, myPersonalDetails); }) @@ -228,6 +187,38 @@ function getFromReportParticipants(reports) { }); } +/** + * Merges partial details object into the local store. + * + * @param {Object} details + * @private + */ +function mergeLocalPersonalDetails(details) { + // We are merging the partial details provided to this method with the existing details we have for the user so + // that we don't overwrite any values that may exist in storage. + const mergedDetails = lodashMerge(personalDetails[currentUserEmail], details); + + // displayName is a generated field so we'll use the firstName and lastName + login to update it. + mergedDetails.displayName = getDisplayName(currentUserEmail, mergedDetails); + + // Update the associated Onyx keys + Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, mergedDetails); + Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[currentUserEmail]: mergedDetails}); +} + +/** + * Sets the personal details object for the current user + * + * @param {Object} details + */ +function setPersonalDetails(details) { + API.PersonalDetails_Update({details: JSON.stringify(details)}); + if (details.timezone) { + NameValuePair.set(CONST.NVP.TIMEZONE, details.timezone); + } + mergeLocalPersonalDetails(details); +} + /** * Sets the onyx with the currency list from the network * @returns {Object}