diff --git a/src/ROUTES.js b/src/ROUTES.js
index 6ec7f4cf229cd..470ea9d99eb75 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -23,6 +23,8 @@ export default {
SETTINGS: 'settings',
SETTINGS_PROFILE: 'settings/profile',
SETTINGS_DISPLAY_NAME: 'settings/profile/display-name',
+ SETTINGS_TIMEZONE: 'settings/profile/timezone',
+ SETTINGS_TIMEZONE_SELECT: 'settings/profile/timezone/select',
SETTINGS_PRONOUNS: 'settings/profile/pronouns',
SETTINGS_PREFERENCES: 'settings/preferences',
SETTINGS_WORKSPACES: 'settings/workspaces',
diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js
index 7fce40647c3f5..0475b87b6b8eb 100755
--- a/src/components/OptionsSelector/BaseOptionsSelector.js
+++ b/src/components/OptionsSelector/BaseOptionsSelector.js
@@ -264,6 +264,7 @@ class BaseOptionsSelector extends Component {
forceTextUnreadStyle={this.props.forceTextUnreadStyle}
showTitleTooltip={this.props.showTitleTooltip}
isDisabled={this.props.isDisabled}
+ shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator}
/>
) : ;
return (
diff --git a/src/components/OptionsSelector/optionsSelectorPropTypes.js b/src/components/OptionsSelector/optionsSelectorPropTypes.js
index ae41fe0f81906..885a09e553a4a 100644
--- a/src/components/OptionsSelector/optionsSelectorPropTypes.js
+++ b/src/components/OptionsSelector/optionsSelectorPropTypes.js
@@ -89,6 +89,9 @@ const propTypes = {
/** Whether to show options list */
shouldShowOptions: PropTypes.bool,
+
+ /** Whether to show a line separating options in list */
+ shouldHaveOptionSeparator: PropTypes.bool,
};
const defaultProps = {
@@ -113,6 +116,7 @@ const defaultProps = {
shouldShowOptions: true,
disableArrowKeysActions: false,
isDisabled: false,
+ shouldHaveOptionSeparator: false,
};
export {propTypes, defaultProps};
diff --git a/src/languages/en.js b/src/languages/en.js
index f8f18a73e0190..a9cdd4c7fe964 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -333,6 +333,11 @@ export default {
john: 'John',
doe: 'Doe',
},
+ timezonePage: {
+ timezone: 'Timezone',
+ isShownOnProfile: 'Your timezone is shown on your profile.',
+ getLocationAutomatically: 'Automatically determine your location.',
+ },
addSecondaryLoginPage: {
addPhoneNumber: 'Add phone number',
addEmailAddress: 'Add email address',
diff --git a/src/languages/es.js b/src/languages/es.js
index 572859d593775..71689626d2853 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -333,6 +333,11 @@ export default {
john: 'Juan',
doe: 'Nadie',
},
+ timezonePage: {
+ timezone: 'Zona horaria',
+ isShownOnProfile: 'Tu zona horaria se muestra en tu perfil.',
+ getLocationAutomatically: 'Detecta tu ubicación automáticamente.',
+ },
addSecondaryLoginPage: {
addPhoneNumber: 'Agregar número de teléfono',
addEmailAddress: 'Agregar dirección de email',
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 4f54ca2015bff..e95581674fab6 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -231,6 +231,20 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
name: 'Settings_Display_Name',
},
+ {
+ getComponent: () => {
+ const SettingsTimezoneInitialPage = require('../../../pages/settings/Profile/TimezoneInitialPage').default;
+ return SettingsTimezoneInitialPage;
+ },
+ name: 'Settings_Timezone',
+ },
+ {
+ getComponent: () => {
+ const SettingsTimezoneSelectPage = require('../../../pages/settings/Profile/TimezoneSelectPage').default;
+ return SettingsTimezoneSelectPage;
+ },
+ name: 'Settings_Timezone_Select',
+ },
{
getComponent: () => {
const SettingsAddSecondaryLoginPage = require('../../../pages/settings/AddSecondaryLoginPage').default;
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 09dbe7fe0e5c4..c44a28bcc10e4 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -96,6 +96,14 @@ export default {
path: ROUTES.SETTINGS_DISPLAY_NAME,
exact: true,
},
+ Settings_Timezone: {
+ path: ROUTES.SETTINGS_TIMEZONE,
+ exact: true,
+ },
+ Settings_Timezone_Select: {
+ path: ROUTES.SETTINGS_TIMEZONE_SELECT,
+ exact: true,
+ },
Settings_About: {
path: ROUTES.SETTINGS_ABOUT,
exact: true,
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index 66ec00ca992b3..b779aa63a2863 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -337,6 +337,56 @@ function updateDisplayName(firstName, lastName) {
Navigation.navigate(ROUTES.SETTINGS_PROFILE);
}
+/**
+ * Updates timezone's 'automatic' setting, and updates
+ * selected timezone if set to automatically update.
+ *
+ * @param {Object} timezone
+ * @param {Boolean} timezone.automatic
+ * @param {String} timezone.selected
+ */
+function updateAutomaticTimezone(timezone) {
+ API.write('UpdateAutomaticTimezone', {
+ timezone: JSON.stringify(timezone),
+ }, {
+ optimisticData: [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.PERSONAL_DETAILS,
+ value: {
+ [currentUserEmail]: {
+ timezone,
+ },
+ },
+ }],
+ });
+}
+
+/**
+ * Updates user's 'selected' timezone, then navigates to the
+ * initial Timezone page.
+ *
+ * @param {String} selectedTimezone
+ */
+function updateSelectedTimezone(selectedTimezone) {
+ const timezone = {
+ selected: selectedTimezone,
+ };
+ API.write('UpdateSelectedTimezone', {
+ timezone: JSON.stringify(timezone),
+ }, {
+ optimisticData: [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.PERSONAL_DETAILS,
+ value: {
+ [currentUserEmail]: {
+ timezone,
+ },
+ },
+ }],
+ });
+ Navigation.navigate(ROUTES.SETTINGS_TIMEZONE);
+}
+
/**
* Fetches the local currency based on location and sets currency code/symbol to Onyx
*/
@@ -452,4 +502,6 @@ export {
updateDisplayName,
updatePronouns,
clearAvatarErrors,
+ updateAutomaticTimezone,
+ updateSelectedTimezone,
};
diff --git a/src/pages/settings/Profile/TimezoneInitialPage.js b/src/pages/settings/Profile/TimezoneInitialPage.js
new file mode 100644
index 0000000000000..260820710cd8c
--- /dev/null
+++ b/src/pages/settings/Profile/TimezoneInitialPage.js
@@ -0,0 +1,85 @@
+import lodashGet from 'lodash/get';
+import React from 'react';
+import {View} from 'react-native';
+import moment from 'moment-timezone';
+import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../components/withCurrentUserPersonalDetails';
+import ScreenWrapper from '../../../components/ScreenWrapper';
+import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton';
+import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
+import ROUTES from '../../../ROUTES';
+import CONST from '../../../CONST';
+import Text from '../../../components/Text';
+import styles from '../../../styles/styles';
+import Navigation from '../../../libs/Navigation/Navigation';
+import * as PersonalDetails from '../../../libs/actions/PersonalDetails';
+import compose from '../../../libs/compose';
+import Switch from '../../../components/Switch';
+import MenuItemWithTopDescription from '../../../components/MenuItemWithTopDescription';
+
+const propTypes = {
+ ...withLocalizePropTypes,
+ ...withCurrentUserPersonalDetailsPropTypes,
+};
+
+const defaultProps = {
+ ...withCurrentUserPersonalDetailsDefaultProps,
+};
+
+const TimezoneInitialPage = (props) => {
+ const timezone = lodashGet(props.currentUserPersonalDetails, 'timezone', CONST.DEFAULT_TIME_ZONE);
+
+ /**
+ * Updates setting for automatic timezone selection.
+ * Note: If we are updating automatically, we'll immediately calculate the user's timezone.
+ *
+ * @param {Boolean} isAutomatic
+ */
+ const updateAutomaticTimezone = (isAutomatic) => {
+ PersonalDetails.updateAutomaticTimezone({
+ automatic: isAutomatic,
+ selected: isAutomatic ? moment.tz.guess() : timezone.selected,
+ });
+ };
+
+ return (
+
+ Navigation.navigate(ROUTES.SETTINGS_PROFILE)}
+ onCloseButtonPress={() => Navigation.dismissModal(true)}
+ />
+
+
+ {props.translate('timezonePage.isShownOnProfile')}
+
+
+
+ {props.translate('timezonePage.getLocationAutomatically')}
+
+
+
+
+ Navigation.navigate(ROUTES.SETTINGS_TIMEZONE_SELECT)}
+ />
+
+ );
+};
+
+TimezoneInitialPage.propTypes = propTypes;
+TimezoneInitialPage.defaultProps = defaultProps;
+TimezoneInitialPage.displayName = 'TimezoneInitialPage';
+
+export default compose(
+ withLocalize,
+ withCurrentUserPersonalDetails,
+)(TimezoneInitialPage);
diff --git a/src/pages/settings/Profile/TimezoneSelectPage.js b/src/pages/settings/Profile/TimezoneSelectPage.js
new file mode 100644
index 0000000000000..62cb212d60a41
--- /dev/null
+++ b/src/pages/settings/Profile/TimezoneSelectPage.js
@@ -0,0 +1,103 @@
+import lodashGet from 'lodash/get';
+import React, {Component} from 'react';
+import _ from 'underscore';
+import moment from 'moment-timezone';
+import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../components/withCurrentUserPersonalDetails';
+import ScreenWrapper from '../../../components/ScreenWrapper';
+import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton';
+import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
+import ROUTES from '../../../ROUTES';
+import CONST from '../../../CONST';
+import styles from '../../../styles/styles';
+import Navigation from '../../../libs/Navigation/Navigation';
+import * as PersonalDetails from '../../../libs/actions/PersonalDetails';
+import compose from '../../../libs/compose';
+import OptionsSelector from '../../../components/OptionsSelector';
+import themeColors from '../../../styles/themes/default';
+import * as Expensicons from '../../../components/Icon/Expensicons';
+
+const propTypes = {
+ ...withLocalizePropTypes,
+ ...withCurrentUserPersonalDetailsPropTypes,
+};
+
+const defaultProps = {
+ ...withCurrentUserPersonalDetailsDefaultProps,
+};
+
+class TimezoneSelectPage extends Component {
+ constructor(props) {
+ super(props);
+
+ this.saveSelectedTimezone = this.saveSelectedTimezone.bind(this);
+ this.filterShownTimezones = this.filterShownTimezones.bind(this);
+
+ this.currentSelectedTimezone = lodashGet(props.currentUserPersonalDetails, 'timezone.selected', CONST.DEFAULT_TIME_ZONE.selected);
+ this.allTimezones = _.chain(moment.tz.names())
+ .filter(timezone => !timezone.startsWith('Etc/GMT'))
+ .map(timezone => ({
+ text: timezone,
+ keyForList: timezone,
+
+ // Add green checkmark icon & bold the timezone text
+ customIcon: timezone === this.currentSelectedTimezone
+ ? {src: Expensicons.Checkmark, color: themeColors.success}
+ : null,
+ isUnread: timezone === this.currentSelectedTimezone,
+ }))
+ .value();
+
+ this.state = {
+ timezoneInputText: this.currentSelectedTimezone,
+ timezoneOptions: this.allTimezones,
+ };
+ }
+
+ /**
+ * @param {Object} timezone
+ * @param {String} timezone.text
+ */
+ saveSelectedTimezone({text}) {
+ PersonalDetails.updateSelectedTimezone(text);
+ }
+
+ /**
+ * @param {String} searchText
+ */
+ filterShownTimezones(searchText) {
+ this.setState({
+ timezoneInputText: searchText,
+ timezoneOptions: _.filter(this.allTimezones, (tz => tz.text.toLowerCase().includes(searchText.toLowerCase()))),
+ });
+ }
+
+ render() {
+ return (
+
+ Navigation.navigate(ROUTES.SETTINGS_TIMEZONE)}
+ onCloseButtonPress={() => Navigation.dismissModal(true)}
+ />
+
+
+ );
+ }
+}
+
+TimezoneSelectPage.propTypes = propTypes;
+TimezoneSelectPage.defaultProps = defaultProps;
+
+export default compose(
+ withLocalize,
+ withCurrentUserPersonalDetails,
+)(TimezoneSelectPage);