diff --git a/src/ROUTES.js b/src/ROUTES.js index 470ea9d99eb7..b7c1e537bc90 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -27,6 +27,8 @@ export default { SETTINGS_TIMEZONE_SELECT: 'settings/profile/timezone/select', SETTINGS_PRONOUNS: 'settings/profile/pronouns', SETTINGS_PREFERENCES: 'settings/preferences', + SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode', + SETTINGS_LANGUAGE: 'settings/preferences/language', SETTINGS_WORKSPACES: 'settings/workspaces', SETTINGS_SECURITY: 'settings/security', SETTINGS_CLOSE: 'settings/security/closeAccount', diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js index dece444a303f..747433962d9b 100644 --- a/src/components/LocalePicker.js +++ b/src/components/LocalePicker.js @@ -34,11 +34,11 @@ const defaultProps = { const localesToLanguages = { default: { value: 'en', - label: Localize.translate('en', 'preferencesPage.languages.english'), + label: Localize.translate('en', 'languagePage.languages.en.label'), }, es: { value: 'es', - label: Localize.translate('es', 'preferencesPage.languages.spanish'), + label: Localize.translate('es', 'languagePage.languages.es.label'), }, }; @@ -49,7 +49,7 @@ const LocalePicker = (props) => { return ( { if (locale === props.preferredLocale) { return; diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index f31def05820f..d5933003dda9 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -58,6 +58,9 @@ const propTypes = { /** Whether to show a line separating options in list */ shouldHaveOptionSeparator: PropTypes.bool, + /** Whether to remove the lateral padding and align the content with the margins */ + shouldDisableRowInnerPadding: PropTypes.bool, + style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), ...withLocalizePropTypes, @@ -74,6 +77,7 @@ const defaultProps = { optionIsFocused: false, style: null, shouldHaveOptionSeparator: false, + shouldDisableRowInnerPadding: false, }; class OptionRow extends Component { @@ -172,7 +176,7 @@ class OptionRow extends Component { styles.alignItemsCenter, styles.justifyContentBetween, styles.sidebarLink, - styles.sidebarLinkInner, + this.props.shouldDisableRowInnerPadding ? null : styles.sidebarLinkInner, this.props.optionIsFocused ? styles.sidebarLinkActive : null, hovered && !this.props.optionIsFocused ? this.props.hoverStyle : null, this.props.isDisabled && styles.cursorDisabled, diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 4a8208b06a1b..38f0d7c9be3f 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -172,6 +172,7 @@ class BaseOptionsList extends Component { boldStyle={this.props.boldStyle} isDisabled={isDisabled} shouldHaveOptionSeparator={index > 0 && this.props.shouldHaveOptionSeparator} + shouldDisableRowInnerPadding={this.props.shouldDisableRowInnerPadding} /> ); } diff --git a/src/components/OptionsList/optionsListPropTypes.js b/src/components/OptionsList/optionsListPropTypes.js index 0ca8b7a70901..91aaa5988d57 100644 --- a/src/components/OptionsList/optionsListPropTypes.js +++ b/src/components/OptionsList/optionsListPropTypes.js @@ -70,6 +70,9 @@ const propTypes = { /** Whether to show a line separating options in list */ shouldHaveOptionSeparator: PropTypes.bool, + + /** Whether to disable the inner padding in rows */ + shouldDisableRowInnerPadding: PropTypes.bool, }; const defaultProps = { @@ -90,6 +93,7 @@ const defaultProps = { isDisabled: false, onLayout: undefined, shouldHaveOptionSeparator: false, + shouldDisableRowInnerPadding: false, }; export {propTypes, defaultProps}; diff --git a/src/languages/en.js b/src/languages/en.js index dddf50173b61..b2f71ddd2853 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -493,16 +493,31 @@ export default { defaultPaymentMethod: 'Default', }, preferencesPage: { - mostRecent: 'Most recent', - mostRecentModeDescription: 'This will display all chats by default, sorted by most recent, with pinned items at the top.', - focus: '#focus', - focusModeDescription: '#focus – This will only display unread and pinned chats, all sorted alphabetically.', receiveRelevantFeatureUpdatesAndExpensifyNews: 'Receive relevant feature updates and Expensify news', + }, + priorityModePage: { priorityMode: 'Priority mode', + explainerText: 'Choose whether to show all chats by default sorted with most recent with pinned items at the top, or #focus on unread pinned items, sorted alphabetically.', + priorityModes: { + default: { + label: 'Most recent', + description: 'Show all chats sorted by most recent', + }, + gsd: { + label: '#focus', + description: 'Only show unread sorted alphabetically', + }, + }, + }, + languagePage: { language: 'Language', languages: { - english: 'English', - spanish: 'Spanish', + en: { + label: 'English', + }, + es: { + label: 'Spanish', + }, }, }, signInPage: { diff --git a/src/languages/es.js b/src/languages/es.js index ef5bf45b1a10..778fe4ee3729 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -493,16 +493,31 @@ export default { defaultPaymentMethod: 'Predeterminado', }, preferencesPage: { - mostRecent: 'Más recientes', - mostRecentModeDescription: 'Esta opción muestra por defecto todos los chats, ordenados a partir del más reciente, con los chats destacados arriba de todo.', - focus: '#concentración', - focusModeDescription: '#concentración – Muestra sólo los chats no leídos y destacados ordenados alfabéticamente.', receiveRelevantFeatureUpdatesAndExpensifyNews: 'Recibir noticias sobre Expensify y actualizaciones del producto', + }, + priorityModePage: { priorityMode: 'Modo prioridad', + explainerText: 'Elija si desea mostrar por defecto todos los chats ordenados desde el más reciente y con los elementos anclados en la parte superior, o elija el modo #concentración, con los elementos no leídos anclados en la parte superior y ordenados alfabéticamente.', + priorityModes: { + default: { + label: 'Más recientes', + description: 'Mostrar todos los chats ordenados desde el más reciente', + }, + gsd: { + label: '#concentración', + description: 'Mostrar sólo los no leídos ordenados alfabéticamente', + }, + }, + }, + languagePage: { language: 'Idioma', languages: { - english: 'Inglés', - spanish: 'Español', + en: { + label: 'Inglés', + }, + es: { + label: 'Español', + }, }, }, signInPage: { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index e95581674fab..5a58ea3f510c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -254,11 +254,25 @@ const SettingsModalStackNavigator = createModalStackNavigator([ }, { getComponent: () => { - const SettingsPreferencesPage = require('../../../pages/settings/PreferencesPage').default; + const SettingsPreferencesPage = require('../../../pages/settings/Preferences/PreferencesPage').default; return SettingsPreferencesPage; }, name: 'Settings_Preferences', }, + { + getComponent: () => { + const SettingsPreferencesPriorityModePage = require('../../../pages/settings/Preferences/PriorityModePage').default; + return SettingsPreferencesPriorityModePage; + }, + name: 'Settings_Preferences_PriorityMode', + }, + { + getComponent: () => { + const SettingsPreferencesLanguagePage = require('../../../pages/settings/Preferences/LanguagePage').default; + return SettingsPreferencesLanguagePage; + }, + name: 'Settings_Preferences_Language', + }, { getComponent: () => { const SettingsPasswordPage = require('../../../pages/settings/PasswordPage').default; diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index e9bbeb987d25..4b5b9c168bbf 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -44,6 +44,14 @@ export default { path: ROUTES.SETTINGS_PREFERENCES, exact: true, }, + Settings_Preferences_PriorityMode: { + path: ROUTES.SETTINGS_PRIORITY_MODE, + exact: true, + }, + Settings_Preferences_Language: { + path: ROUTES.SETTINGS_LANGUAGE, + exact: true, + }, Settings_Close: { path: ROUTES.SETTINGS_CLOSE, exact: true, diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index dd23484176ec..a74a7350e06a 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -402,6 +402,7 @@ function updateChatPriorityMode(mode) { API.write('UpdateChatPriorityMode', { value: mode, }, {optimisticData}); + Navigation.navigate(ROUTES.SETTINGS_PREFERENCES); } /** diff --git a/src/pages/settings/Preferences/LanguagePage.js b/src/pages/settings/Preferences/LanguagePage.js new file mode 100644 index 000000000000..92d16b2f9190 --- /dev/null +++ b/src/pages/settings/Preferences/LanguagePage.js @@ -0,0 +1,77 @@ +import _ from 'underscore'; +import React from 'react'; +import PropTypes from 'prop-types'; +import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; +import ScreenWrapper from '../../../components/ScreenWrapper'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import Navigation from '../../../libs/Navigation/Navigation'; +import ROUTES from '../../../ROUTES'; +import OptionsList from '../../../components/OptionsList'; +import styles from '../../../styles/styles'; +import themeColors from '../../../styles/themes/default'; +import * as Expensicons from '../../../components/Icon/Expensicons'; +import * as App from '../../../libs/actions/App'; + +const greenCheckmark = {src: Expensicons.Checkmark, color: themeColors.success}; + +const propTypes = { + ...withLocalizePropTypes, + + /** The preferred language of the App */ + preferredLocale: PropTypes.string.isRequired, +}; + +const LanguagePage = (props) => { + const localesToLanguages = _.map(props.translate('languagePage.languages'), + (language, key) => ( + { + value: key, + text: language.label, + keyForList: key, + + // Include the green checkmark icon to indicate the currently selected value + customIcon: props.preferredLocale === key ? greenCheckmark : undefined, + + // This property will make the currently selected value have bold text + boldStyle: props.preferredLocale === key, + } + )); + + return ( + + Navigation.navigate(ROUTES.SETTINGS_PREFERENCES)} + onCloseButtonPress={() => Navigation.dismissModal(true)} + /> + { + if (language.value !== props.preferredLocale) { + App.setLocale(language.value); + } + Navigation.navigate(ROUTES.SETTINGS_PREFERENCES); + } + } + hideSectionHeaders + optionHoveredStyle={ + { + ...styles.hoveredComponentBG, + ...styles.mhn5, + ...styles.ph5, + } + } + shouldHaveOptionSeparator + shouldDisableRowInnerPadding + contentContainerStyles={[styles.ph5]} + /> + + ); +}; + +LanguagePage.displayName = 'LanguagePage'; +LanguagePage.propTypes = propTypes; + +export default withLocalize(LanguagePage); diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js new file mode 100755 index 000000000000..3260fda5e5ca --- /dev/null +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -0,0 +1,118 @@ +import _ from 'underscore'; +import lodashGet from 'lodash/get'; +import React from 'react'; +import {View, ScrollView} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; +import Navigation from '../../../libs/Navigation/Navigation'; +import ROUTES from '../../../ROUTES'; +import ONYXKEYS from '../../../ONYXKEYS'; +import styles from '../../../styles/styles'; +import Text from '../../../components/Text'; +import CONST from '../../../CONST'; +import * as User from '../../../libs/actions/User'; +import ScreenWrapper from '../../../components/ScreenWrapper'; +import Switch from '../../../components/Switch'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import compose from '../../../libs/compose'; +import withEnvironment, {environmentPropTypes} from '../../../components/withEnvironment'; +import TestToolMenu from '../../../components/TestToolMenu'; +import MenuItemWithTopDescription from '../../../components/MenuItemWithTopDescription'; + +const propTypes = { + /** The chat priority mode */ + priorityMode: PropTypes.string, + + /** The details about the user that is signed in */ + user: PropTypes.shape({ + /** Whether or not the user is subscribed to news updates */ + isSubscribedToNewsletter: PropTypes.bool, + }), + + /** The preferred language of the App */ + preferredLocale: PropTypes.string.isRequired, + + ...withLocalizePropTypes, + ...environmentPropTypes, +}; + +const defaultProps = { + priorityMode: CONST.PRIORITY_MODE.DEFAULT, + user: {}, +}; + +const PreferencesPage = (props) => { + const priorityModes = props.translate('priorityModePage.priorityModes'); + const languages = props.translate('languagePage.languages'); + + return ( + + Navigation.navigate(ROUTES.SETTINGS)} + onCloseButtonPress={() => Navigation.dismissModal(true)} + /> + + + + {props.translate('common.notifications')} + + + + + {props.translate('preferencesPage.receiveRelevantFeatureUpdatesAndExpensifyNews')} + + + + + + + Navigation.navigate(ROUTES.SETTINGS_PRIORITY_MODE)} + /> + Navigation.navigate(ROUTES.SETTINGS_LANGUAGE)} + /> + + {/* If we are in the staging environment then we enable additional test features */} + { + _.contains([CONST.ENVIRONMENT.STAGING, CONST.ENVIRONMENT.DEV], props.environment) + && ( + + + + ) + } + + + + ); +}; + +PreferencesPage.propTypes = propTypes; +PreferencesPage.defaultProps = defaultProps; +PreferencesPage.displayName = 'PreferencesPage'; + +export default compose( + withEnvironment, + withLocalize, + withOnyx({ + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + }, + user: { + key: ONYXKEYS.USER, + }, + }), +)(PreferencesPage); diff --git a/src/pages/settings/Preferences/PriorityModePage.js b/src/pages/settings/Preferences/PriorityModePage.js new file mode 100644 index 000000000000..16af2569b824 --- /dev/null +++ b/src/pages/settings/Preferences/PriorityModePage.js @@ -0,0 +1,80 @@ +import _, {compose} from 'underscore'; +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; +import ScreenWrapper from '../../../components/ScreenWrapper'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import Navigation from '../../../libs/Navigation/Navigation'; +import ROUTES from '../../../ROUTES'; +import OptionsList from '../../../components/OptionsList'; +import styles from '../../../styles/styles'; +import Text from '../../../components/Text'; +import themeColors from '../../../styles/themes/default'; +import * as Expensicons from '../../../components/Icon/Expensicons'; +import ONYXKEYS from '../../../ONYXKEYS'; +import * as User from '../../../libs/actions/User'; + +const greenCheckmark = {src: Expensicons.Checkmark, color: themeColors.success}; + +const propTypes = { + ...withLocalizePropTypes, +}; + +const PriorityModePage = (props) => { + const priorityModes = _.map(props.translate('priorityModePage.priorityModes'), + (mode, key) => ( + { + value: key, + text: mode.label, + alternateText: mode.description, + keyForList: key, + + // Include the green checkmark icon to indicate the currently selected value + customIcon: props.priorityMode === key ? greenCheckmark : undefined, + + // This property will make the currently selected value have bold text + boldStyle: props.priorityMode === key, + } + )); + + return ( + + Navigation.navigate(ROUTES.SETTINGS_PREFERENCES)} + onCloseButtonPress={() => Navigation.dismissModal(true)} + /> + + {props.translate('priorityModePage.explainerText')} + + User.updateChatPriorityMode(mode.value)} + hideSectionHeaders + optionHoveredStyle={ + { + ...styles.hoveredComponentBG, + ...styles.mhn5, + ...styles.ph5, + } + } + shouldHaveOptionSeparator + shouldDisableRowInnerPadding + contentContainerStyles={[styles.ph5]} + /> + + ); +}; + +PriorityModePage.displayName = 'PriorityModePage'; +PriorityModePage.propTypes = propTypes; + +export default compose( + withLocalize, + withOnyx({ + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + }, + }), +)(PriorityModePage); diff --git a/src/pages/settings/PreferencesPage.js b/src/pages/settings/PreferencesPage.js deleted file mode 100755 index ee4a39ddc404..000000000000 --- a/src/pages/settings/PreferencesPage.js +++ /dev/null @@ -1,125 +0,0 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; -import React from 'react'; -import {View, ScrollView} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import PropTypes from 'prop-types'; - -import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; -import LocalePicker from '../../components/LocalePicker'; -import Navigation from '../../libs/Navigation/Navigation'; -import ROUTES from '../../ROUTES'; -import ONYXKEYS from '../../ONYXKEYS'; -import styles from '../../styles/styles'; -import Text from '../../components/Text'; -import CONST from '../../CONST'; -import * as User from '../../libs/actions/User'; -import ScreenWrapper from '../../components/ScreenWrapper'; -import Switch from '../../components/Switch'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import compose from '../../libs/compose'; -import Picker from '../../components/Picker'; -import withEnvironment, {environmentPropTypes} from '../../components/withEnvironment'; -import TestToolMenu from '../../components/TestToolMenu'; - -const propTypes = { - /** The chat priority mode */ - priorityMode: PropTypes.string, - - /** The details about the user that is signed in */ - user: PropTypes.shape({ - /** Whether or not the user is subscribed to news updates */ - isSubscribedToNewsletter: PropTypes.bool, - shouldUseStagingServer: PropTypes.bool, - }), - - ...withLocalizePropTypes, - ...environmentPropTypes, -}; - -const defaultProps = { - priorityMode: CONST.PRIORITY_MODE.DEFAULT, - user: {}, -}; - -const PreferencesPage = (props) => { - const priorityModes = { - default: { - value: CONST.PRIORITY_MODE.DEFAULT, - label: props.translate('preferencesPage.mostRecent'), - description: props.translate('preferencesPage.mostRecentModeDescription'), - }, - gsd: { - value: CONST.PRIORITY_MODE.GSD, - label: props.translate('preferencesPage.focus'), - description: props.translate('preferencesPage.focusModeDescription'), - }, - }; - - return ( - - Navigation.navigate(ROUTES.SETTINGS)} - onCloseButtonPress={() => Navigation.dismissModal(true)} - /> - - - - {props.translate('common.notifications')} - - - - - {props.translate('preferencesPage.receiveRelevantFeatureUpdatesAndExpensifyNews')} - - - - - - - - User.updateChatPriorityMode(mode) - } - items={_.values(priorityModes)} - value={props.priorityMode} - /> - - - {priorityModes[props.priorityMode].description} - - - - - - {/* If we are in the staging environment then we enable additional test features */} - {_.contains([CONST.ENVIRONMENT.STAGING, CONST.ENVIRONMENT.DEV], props.environment) && } - - - - ); -}; - -PreferencesPage.propTypes = propTypes; -PreferencesPage.defaultProps = defaultProps; -PreferencesPage.displayName = 'PreferencesPage'; - -export default compose( - withEnvironment, - withLocalize, - withOnyx({ - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - }, - user: { - key: ONYXKEYS.USER, - }, - }), -)(PreferencesPage); diff --git a/src/styles/utilities/spacing.js b/src/styles/utilities/spacing.js index d1683cead9b6..08259eaf77e9 100644 --- a/src/styles/utilities/spacing.js +++ b/src/styles/utilities/spacing.js @@ -45,6 +45,14 @@ export default { marginHorizontal: 20, }, + mh8: { + marginHorizontal: 32, + }, + + mhn5: { + marginHorizontal: -20, + }, + mv1: { marginVertical: 4, }, @@ -89,6 +97,10 @@ export default { marginRight: 20, }, + mr8: { + marginRight: 32, + }, + mrn5: { marginRight: -20, },