Skip to content

Commit

Permalink
Merge pull request #12201 from Expensify/beaman-newTimezonePage
Browse files Browse the repository at this point in the history
New timezone pages
  • Loading branch information
cristipaval authored Dec 1, 2022
2 parents 383ce45 + 717fbc4 commit 80b4c67
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions src/components/OptionsSelector/BaseOptionsSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ class BaseOptionsSelector extends Component {
forceTextUnreadStyle={this.props.forceTextUnreadStyle}
showTitleTooltip={this.props.showTitleTooltip}
isDisabled={this.props.isDisabled}
shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator}
/>
) : <FullScreenLoadingIndicator />;
return (
Expand Down
4 changes: 4 additions & 0 deletions src/components/OptionsSelector/optionsSelectorPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -113,6 +116,7 @@ const defaultProps = {
shouldShowOptions: true,
disableArrowKeysActions: false,
isDisabled: false,
shouldHaveOptionSeparator: false,
};

export {propTypes, defaultProps};
5 changes: 5 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
14 changes: 14 additions & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
52 changes: 52 additions & 0 deletions src/libs/actions/PersonalDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,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
*/
Expand Down Expand Up @@ -328,4 +378,6 @@ export {
updateDisplayName,
updatePronouns,
clearAvatarErrors,
updateAutomaticTimezone,
updateSelectedTimezone,
};
85 changes: 85 additions & 0 deletions src/pages/settings/Profile/TimezoneInitialPage.js
Original file line number Diff line number Diff line change
@@ -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 (
<ScreenWrapper>
<HeaderWithCloseButton
title={props.translate('timezonePage.timezone')}
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_PROFILE)}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<View style={[styles.ph5]}>
<Text style={[styles.mb5]}>
{props.translate('timezonePage.isShownOnProfile')}
</Text>
<View style={[styles.flexRow, styles.mb5, styles.alignItemsCenter, styles.justifyContentBetween]}>
<Text>
{props.translate('timezonePage.getLocationAutomatically')}
</Text>
<Switch
isOn={timezone.automatic}
onToggle={updateAutomaticTimezone}
/>
</View>
</View>
<MenuItemWithTopDescription
title={timezone.selected}
description={props.translate('timezonePage.timezone')}
shouldShowRightIcon
wrapperStyle={[styles.ph2, styles.mb3]}
disabled={timezone.automatic}
onPress={() => Navigation.navigate(ROUTES.SETTINGS_TIMEZONE_SELECT)}
/>
</ScreenWrapper>
);
};

TimezoneInitialPage.propTypes = propTypes;
TimezoneInitialPage.defaultProps = defaultProps;
TimezoneInitialPage.displayName = 'TimezoneInitialPage';

export default compose(
withLocalize,
withCurrentUserPersonalDetails,
)(TimezoneInitialPage);
103 changes: 103 additions & 0 deletions src/pages/settings/Profile/TimezoneSelectPage.js
Original file line number Diff line number Diff line change
@@ -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 (
<ScreenWrapper>
<HeaderWithCloseButton
title={this.props.translate('timezonePage.timezone')}
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_TIMEZONE)}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<OptionsSelector
textInputLabel={this.props.translate('timezonePage.timezone')}
value={this.state.timezoneInputText}
onChangeText={this.filterShownTimezones}
onSelectRow={this.saveSelectedTimezone}
optionHoveredStyle={styles.hoveredComponentBG}
sections={[{data: this.state.timezoneOptions}]}
shouldHaveOptionSeparator
/>
</ScreenWrapper>
);
}
}

TimezoneSelectPage.propTypes = propTypes;
TimezoneSelectPage.defaultProps = defaultProps;

export default compose(
withLocalize,
withCurrentUserPersonalDetails,
)(TimezoneSelectPage);

0 comments on commit 80b4c67

Please sign in to comment.