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 12 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 @@ -22,6 +22,8 @@ export default {
HOME: '',
SETTINGS: 'settings',
SETTINGS_PROFILE: 'settings/profile',
SETTINGS_TIMEZONE_INITIAL: 'settings/profile/timezone',
SETTINGS_TIMEZONE_SELECT: 'settings/profile/timezone/select',
SETTINGS_PREFERENCES: 'settings/preferences',
SETTINGS_WORKSPACES: 'settings/workspaces',
SETTINGS_SECURITY: 'settings/security',
Expand Down
6 changes: 6 additions & 0 deletions src/components/OptionRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ const OptionRow = (props) => {
<Hoverable
containerStyles={[
props.isDisabled ? styles.userSelectNone : null,
styles.borderBottom,
Beamanator marked this conversation as resolved.
Show resolved Hide resolved
]}
>
{hovered => (
Expand Down Expand Up @@ -266,6 +267,11 @@ const OptionRow = (props) => {
<Icon src={Expensicons.Pin} height={16} width={16} />
</View>
)}
{Boolean(props.option.customIcon) && (
<View>
<Icon src={props.option.customIcon.src} height={16} width={16} fill={props.option.customIcon.color} />
</View>
)}
Beamanator marked this conversation as resolved.
Show resolved Hide resolved
</View>
)}
</TouchableOpacity>
Expand Down
6 changes: 6 additions & 0 deletions src/components/optionPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export default PropTypes.shape({
// Whether the option has an outstanding IOU
hasOutstandingIOU: PropTypes.bool,

// Custom icon to render on the right side of the option
customIcon: PropTypes.shape({
src: PropTypes.func,
color: PropTypes.string,
}),

// List of participants of the report
participantsList: PropTypes.arrayOf(participantPropTypes),

Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ export default {
offline: 'Offline',
syncing: 'Syncing',
},
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 @@ -302,6 +302,11 @@ export default {
offline: 'Desconectado',
syncing: 'Sincronizando',
},
timezonePage: {
timezone: 'Zona horaria',
isShownOnProfile: '',
getLocationAutomatically: '',
cristipaval marked this conversation as resolved.
Show resolved Hide resolved
},
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 @@ -217,6 +217,20 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
name: 'Settings_Profile',
},
{
getComponent: () => {
const SettingsTimezoneInitialPage = require('../../../pages/settings/Profile/TimezoneInitialPage').default;
return SettingsTimezoneInitialPage;
},
name: 'Settings_Timezone_Init',
NikkiWines marked this conversation as resolved.
Show resolved Hide resolved
},
{
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 @@ -88,6 +88,14 @@ export default {
path: ROUTES.SETTINGS_PROFILE,
exact: true,
},
Settings_Timezone_Init: {
NikkiWines marked this conversation as resolved.
Show resolved Hide resolved
path: ROUTES.SETTINGS_TIMEZONE_INITIAL,
exact: true,
},
Settings_Timezone_Select: {
path: ROUTES.SETTINGS_TIMEZONE_SELECT,
exact: true,
},
Settings_About: {
path: ROUTES.SETTINGS_ABOUT,
exact: true,
Expand Down
50 changes: 50 additions & 0 deletions src/libs/actions/PersonalDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import * as LoginUtils from '../LoginUtils';
import * as ReportUtils from '../ReportUtils';
import Growl from '../Growl';
import * as Localize from '../Localize';
import ROUTES from '../../ROUTES';
import Navigation from '../Navigation/Navigation';

let currentUserEmail = '';
Onyx.connect({
Expand Down Expand Up @@ -285,6 +287,52 @@ function updateProfile(firstName, lastName, pronouns, timezone) {
});
}

/**
* 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,
},
},
}],
});
}

/**
* @param {String} selectedTimezone
*/
function updateSelectedTimezone(selectedTimezone) {
API.write('UpdateSelectedTimezone', {
text: selectedTimezone,
}, {
optimisticData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[currentUserEmail]: {
timezone: {
selected: selectedTimezone,
},
},
},
}],
});
Navigation.navigate(ROUTES.SETTINGS_TIMEZONE_INITIAL);
}

/**
* Fetches the local currency based on location and sets currency code/symbol to Onyx
*/
Expand Down Expand Up @@ -398,4 +446,6 @@ export {
extractFirstAndLastNameFromAvailableDetails,
updateProfile,
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 timezone updating automatically.
* Note: If we are updating automatically, we'll immediately caltulate user timezone.
cristipaval marked this conversation as resolved.
Show resolved Hide resolved
*
* @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);
102 changes: 102 additions & 0 deletions src/pages/settings/Profile/TimezoneSelectPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
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.textSuccess}
: 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().startsWith(searchText.toLowerCase()))),
Beamanator marked this conversation as resolved.
Show resolved Hide resolved
});
}

render() {
return (
<ScreenWrapper>
<HeaderWithCloseButton
title={this.props.translate('timezonePage.timezone')}
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_TIMEZONE_INITIAL)}
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.

/>
</ScreenWrapper>
);
}
}

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

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