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

New timezone pages #12201

Merged
merged 29 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9704e1e
Set up routes for new Timezone pages
Beamanator Oct 27, 2022
c9cad55
New timezone initial page
Beamanator Oct 27, 2022
bcae580
New timezone select page
Beamanator Oct 27, 2022
b173e21
New translations
Beamanator Oct 27, 2022
f1bc673
New timezone commands
Beamanator Oct 27, 2022
cfbdb16
Merge branch 'main' of github.com:Expensify/App into beaman-newTimezo…
Beamanator Oct 28, 2022
8e3bf2e
Add vertical padding to timezone button
Beamanator Oct 28, 2022
b9fd35a
Update selected tz then nav to tz init page
Beamanator Oct 28, 2022
2221e56
Fill out timezone select page & filtering
Beamanator Oct 28, 2022
06dfa1d
Move custom icon to Option props
Beamanator Oct 28, 2022
05e67bf
Fix where timezone comes from
Beamanator Oct 28, 2022
d2afbd9
Fix lots of lint issues
Beamanator Oct 28, 2022
c2c7e70
Add prop for option separator
Beamanator Oct 28, 2022
9e6eb14
Merge branch 'main' of github.com:Expensify/App into beaman-newTimezo…
Beamanator Oct 31, 2022
45951b4
Add all prop comments
Beamanator Oct 31, 2022
c51cc57
Get icon props safely
Beamanator Oct 31, 2022
a7758c8
Add function description
Beamanator Oct 31, 2022
c02e802
Match any timezone that includes searched timezone text
Beamanator Oct 31, 2022
cf1ca69
Merge branch 'main' of github.com:Expensify/App into beaman-newTimezo…
Beamanator Nov 7, 2022
5213617
Merge remote-tracking branch 'origin/main' into beaman-newTimezonePage
cristipaval Nov 15, 2022
97df428
Merge remote-tracking branch 'origin/main' into beaman-newTimezonePage
cristipaval Nov 17, 2022
d61aac0
Fix updateSelectedTimezone operation in PersonalDetails.
cristipaval Nov 21, 2022
467760b
Fix js lint errors
cristipaval Nov 21, 2022
818ac83
Update src/languages/es.js
cristipaval Nov 22, 2022
f13ab5f
Rename routing constants
cristipaval Nov 22, 2022
1ab9ee9
Update doc in TimezoneInitialPage.js
cristipaval Nov 22, 2022
4c1910e
Merge remote-tracking branch 'origin/main' into beaman-newTimezonePage
cristipaval Nov 28, 2022
2be55f9
Fix checkmark color in timezone selector page.
cristipaval Nov 28, 2022
717fbc4
Update doc in TimezoneInitialPage.js
cristipaval Dec 1, 2022
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
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 @@ -264,6 +264,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 @@ -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: [{
NikkiWines marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -452,4 +502,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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB - style: maybe it'll be easier to read the constructor if we create a getAllTimeZones() function in this class.

.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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi all! When we added this new feature of adding a checkmark, we created an inconsistency - Other pages like currency selection didn't get any checkmarks making them unhappy.

It was out of scope of this PR, but we should have created a follow up issue for it!
Thanks for reading :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooooo fair point, thanks for noting that! We always like happy pages and happy code

: 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}]}
Copy link
Member

@rushatgabhane rushatgabhane Nov 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Beamanator
thought: instead of maintaining a state for timezoneOptions, could we pass in a filtered array?
it's gonna re-render when we type.

feel free to ignore if it doesn't make the code cleaner.

shouldHaveOptionSeparator
/>
</ScreenWrapper>
);
}
}

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

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