diff --git a/app/actions/views/account_notifications.js b/app/actions/views/account_notifications.js
index 6b5fae485..c7fc7af9e 100644
--- a/app/actions/views/account_notifications.js
+++ b/app/actions/views/account_notifications.js
@@ -11,12 +11,12 @@ export function handleUpdateUserNotifyProps(notifyProps) {
const config = state.entities.general.config;
const {currentUserId} = state.entities.users;
- const {interval, ...otherProps} = notifyProps;
+ const {interval, user_id, ...otherProps} = notifyProps;
const email = notifyProps.email;
if (config.EnableEmailBatching === 'true' && email !== 'false') {
const emailInterval = [{
- user_id: notifyProps.user_id,
+ user_id,
category: Preferences.CATEGORY_NOTIFICATIONS,
name: Preferences.EMAIL_INTERVAL,
value: interval
diff --git a/app/components/radio_button/index.js b/app/components/radio_button/index.js
new file mode 100644
index 000000000..e27256510
--- /dev/null
+++ b/app/components/radio_button/index.js
@@ -0,0 +1,10 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import RadioButton from './radio_button';
+import RadioButtonGroup from './radio_button_group';
+
+export {
+ RadioButton,
+ RadioButtonGroup
+};
diff --git a/app/components/radio_button/radio_button.js b/app/components/radio_button/radio_button.js
new file mode 100644
index 000000000..74f0ef043
--- /dev/null
+++ b/app/components/radio_button/radio_button.js
@@ -0,0 +1,170 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {Animated, Text, View} from 'react-native';
+import Icon from 'react-native-vector-icons/MaterialIcons';
+
+import {getTheme} from 'app/selectors/preferences';
+import {makeStyleSheetFromTheme} from 'app/utils/theme';
+
+const DISABLED_OPACITY = 0.26;
+const DEFAULT_OPACITY = 1;
+
+class RadioButton extends PureComponent {
+ static propTypes = {
+ label: PropTypes.string,
+ theme: PropTypes.object,
+ value: PropTypes.string,
+ checked: PropTypes.bool,
+ disabled: PropTypes.bool,
+ onCheck: PropTypes.func
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ scaleValue: new Animated.Value(0.001),
+ opacityValue: new Animated.Value(0.1)
+ };
+
+ this.responder = {
+ onStartShouldSetResponder: () => true,
+ onResponderGrant: this.highlight,
+ onResponderRelease: this.handleResponderEnd,
+ onResponderTerminate: this.unHighlight
+ };
+ }
+
+ highlight = () => {
+ Animated.timing(
+ this.state.scaleValue,
+ {
+ toValue: 1,
+ duration: 150
+ }
+ ).start();
+ Animated.timing(
+ this.state.opacityValue,
+ {
+ toValue: 0.1,
+ duration: 100
+ }
+ ).start();
+ };
+
+ unHighlight = () => {
+ Animated.timing(
+ this.state.scaleValue,
+ {
+ toValue: 0.001,
+ duration: 1500
+ }
+ ).start();
+ Animated.timing(
+ this.state.opacityValue,
+ {
+ toValue: 0
+ }
+ ).start();
+ };
+
+ handleResponderEnd = () => {
+ const {checked, disabled, onCheck, value} = this.props;
+
+ if (!checked && !disabled && onCheck) {
+ onCheck(value);
+ }
+
+ this.unHighlight();
+ };
+
+ render() {
+ const {scaleValue, opacityValue} = this.state;
+ const {theme, checked, disabled} = this.props;
+ const styles = getStyleSheet(theme);
+
+ const color = checked ? theme.buttonBg : theme.centerChannelColor;
+ const opacity = disabled ? DISABLED_OPACITY : DEFAULT_OPACITY;
+
+ return (
+
+
+
+
+
+ {this.props.label}
+
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0,0,0,0)',
+ marginBottom: 15
+ },
+ ripple: {
+ position: 'absolute',
+ width: 48,
+ height: 48,
+ borderRadius: 56,
+ top: 6
+ },
+ labelContainer: {
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'center'
+ },
+ label: {
+ color: theme.centerChannelColor,
+ flex: 1,
+ fontSize: 17,
+ marginLeft: 15,
+ textAlignVertical: 'center',
+ includeFontPadding: false
+ }
+ };
+});
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps,
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(RadioButton);
diff --git a/app/components/radio_button/radio_button_group.js b/app/components/radio_button/radio_button_group.js
new file mode 100644
index 000000000..001972d25
--- /dev/null
+++ b/app/components/radio_button/radio_button_group.js
@@ -0,0 +1,99 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {View} from 'react-native';
+import RadioButton from './radio_button';
+
+export default class RadioButtonGroup extends PureComponent {
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ name: PropTypes.string.isRequired,
+ value: PropTypes.string,
+ onSelect: PropTypes.func
+ };
+
+ state = {};
+
+ constructor(props) {
+ super(props);
+
+ if (props.value) {
+ this.state = {
+ selected: props.value
+ };
+ } else {
+ React.Children.map(this.props.children, (option) => {
+ if (option) {
+ const {
+ value,
+ checked
+ } = option.props;
+
+ if (!this.state.selected && checked) {
+ this.state = {
+ selected: value
+ };
+ }
+ }
+ });
+ }
+ }
+
+ get value() {
+ return this.state.selected;
+ }
+
+ set value(value) {
+ this.onChange(value);
+ }
+
+ onChange = (value) => {
+ const {onSelect} = this.props;
+ this.setState({
+ selected: value
+ }, () => {
+ if (onSelect) {
+ onSelect(value);
+ }
+ });
+ };
+
+ render = () => {
+ const options = React.Children.map(this.props.children, (option) => {
+ if (option) {
+ const {
+ value,
+ label,
+ disabled,
+ ...other
+ } = option.props;
+
+ const {name} = this.props;
+
+ return (
+
+ );
+ }
+
+ return null;
+ }, this);
+
+ return (
+
+ {options}
+
+ );
+ };
+}
diff --git a/app/screens/about/about.js b/app/screens/about/about.js
index 2a32c8f94..87a255806 100644
--- a/app/screens/about/about.js
+++ b/app/screens/about/about.js
@@ -288,7 +288,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
},
scrollView: {
flex: 1,
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.03)
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06)
},
scrollViewContent: {
paddingBottom: 30
diff --git a/app/screens/account_notifications/account_notifications.js b/app/screens/account_notifications/account_notifications.js
deleted file mode 100644
index 8c6dff3b0..000000000
--- a/app/screens/account_notifications/account_notifications.js
+++ /dev/null
@@ -1,601 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
-import {injectIntl, intlShape} from 'react-intl';
-import {
- Keyboard,
- Platform,
- ScrollView,
- View
-} from 'react-native';
-
-import {Preferences, RequestStatus} from 'mattermost-redux/constants';
-import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
-
-import Loading from 'app/components/loading';
-import StatusBar from 'app/components/status_bar';
-import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
-import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
-
-import Section from './section';
-import SectionItem from './section_item';
-
-class AccountNotifications extends PureComponent {
- static propTypes = {
- actions: PropTypes.shape({
- handleUpdateUserNotifyProps: PropTypes.func.isRequired
- }),
- config: PropTypes.object.isRequired,
- currentUser: PropTypes.object.isRequired,
- intl: intlShape.isRequired,
- myPreferences: PropTypes.object.isRequired,
- navigator: PropTypes.object,
- theme: PropTypes.object.isRequired,
- updateMeRequest: PropTypes.object.isRequired
- };
-
- saveButton = {
- id: 'save-notifications',
- showAsAction: 'always'
- };
-
- constructor(props) {
- super(props);
-
- const {currentUser} = props;
- const notifyProps = currentUser.notify_props || {};
-
- props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
- props.navigator.setButtons({
- rightButtons: [{
- ...this.saveButton,
- title: props.intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'})
- }]
- });
-
- this.setStateFromNotifyProps(notifyProps);
- }
-
- componentDidMount() {
- if (Platform.OS === 'android') {
- Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
- }
- }
-
- componentWillUnmount() {
- if (Platform.OS === 'android') {
- Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
- }
- }
-
- componentWillReceiveProps(nextProps) {
- const {currentUser, updateMeRequest} = nextProps;
- if (currentUser !== this.props.currentUser) {
- const {notify_props: notifyProps} = currentUser;
- this.setStateFromNotifyProps(notifyProps);
- }
-
- if (this.props.updateMeRequest !== updateMeRequest) {
- switch (updateMeRequest.status) {
- case RequestStatus.STARTED:
- this.savingNotifyProps(true);
- this.setState({error: null, saving: true});
- break;
- case RequestStatus.SUCCESS:
- this.savingNotifyProps(false);
- this.setState({error: null, saving: false});
- this.close();
- break;
- case RequestStatus.FAILURE:
- this.savingNotifyProps(false);
- this.setState({error: updateMeRequest.error, saving: false});
- break;
- }
- }
- }
-
- handleAndroidKeyboard = () => {
- this.refs.mention_keys.getWrappedInstance().blur();
- };
-
- onNavigatorEvent = (event) => {
- if (event.type === 'NavBarButtonPress') {
- if (event.id === 'save-notifications') {
- this.saveUserNotifyProps();
- }
- }
- };
-
- setStateFromNotifyProps = (notifyProps) => {
- const mentionKeys = (notifyProps.mention_keys || '').split(',');
- const usernameMentionIndex = mentionKeys.indexOf(this.props.currentUser.username);
- if (usernameMentionIndex > -1) {
- mentionKeys.splice(usernameMentionIndex, 1);
- }
-
- const email = notifyProps.email;
- let interval;
- if (this.props.config.EnableEmailBatching === 'true') {
- const emailPreferences = getPreferencesByCategory(this.props.myPreferences, Preferences.CATEGORY_NOTIFICATIONS);
- if (emailPreferences.size) {
- interval = emailPreferences.get(Preferences.EMAIL_INTERVAL).value;
- }
- }
-
- const comments = notifyProps.comments || 'never';
-
- const newState = {
- ...notifyProps,
- comments,
- email,
- interval,
- usernameMention: usernameMentionIndex > -1,
- mention_keys: mentionKeys.join(',')
- };
-
- if (this.state) {
- this.setState(newState);
- } else {
- this.state = {...newState};
- }
- };
-
- toggleFirstNameMention = () => {
- this.setState({
- first_name: (!(this.state.first_name === 'true')).toString()
- });
- };
-
- toggleUsernameMention = () => {
- this.setState({
- usernameMention: !this.state.usernameMention
- });
- };
-
- toggleChannelMentions = () => {
- this.setState({
- channel: (!(this.state.channel === 'true')).toString()
- });
- };
-
- focusMentionKeys = () => {
- this.refs.mention_keys.getWrappedInstance().focus();
- };
-
- updateMentionKeys = (text) => {
- this.setState({
- mention_keys: text
- });
- };
-
- savingNotifyProps = (loading) => {
- this.props.navigator.setButtons({
- rightButtons: [{...this.saveButton, disabled: loading}]
- });
- };
-
- setEmailNotifications = (value) => {
- const {config} = this.props;
- let email = value;
- let interval;
-
- const emailBatchingEnabled = config.EnableEmailBatching === 'true';
- if (emailBatchingEnabled && value !== 'false') {
- interval = value;
- email = 'true';
- }
-
- this.setState({
- email,
- interval
- });
- };
-
- setReplyNotifications = (value) => {
- this.setState({
- comments: value
- });
- };
-
- setMobilePush = (value) => {
- this.setState({
- push: value
- });
- };
-
- setMobilePushStatus = (value) => {
- this.setState({
- push_status: value
- });
- };
-
- saveUserNotifyProps = () => {
- let {mention_keys: mentionKeys, usernameMention, ...notifyProps} = this.state; //eslint-disable-line prefer-const
-
- if (mentionKeys.length > 0) {
- mentionKeys = mentionKeys.split(',').map((m) => m.replace(/\s/g, ''));
- } else {
- mentionKeys = [];
- }
-
- if (usernameMention) {
- mentionKeys.push(`${this.props.currentUser.username}`);
- }
-
- mentionKeys = mentionKeys.join(',');
-
- this.props.actions.handleUpdateUserNotifyProps({
- ...notifyProps,
- mention_keys: mentionKeys,
- user_id: this.props.currentUser.id
- });
- };
-
- buildMentionSection = () => {
- const {currentUser, theme} = this.props;
- const style = getStyleSheet(theme);
-
- return (
-
- {currentUser.first_name.length > 0 &&
-
-
-
-
- }
-
-
-
-
-
-
-
-
- );
- };
-
- buildEmailSection = () => {
- const {config, theme} = this.props;
- const style = getStyleSheet(theme);
-
- const sendEmailNotifications = config.SendEmailNotifications === 'true';
- const emailBatchingEnabled = config.EnableEmailBatching === 'true';
-
- let sendImmediatley = this.state.email === 'true';
- let sendImmediatleyValue = 'true';
- let fifteenMinutes;
- let hourly;
- const never = this.state.email === 'false';
-
- if (emailBatchingEnabled && this.state.email !== 'false') {
- sendImmediatley = this.state.interval === Preferences.INTERVAL_IMMEDIATE.toString();
- fifteenMinutes = this.state.interval === Preferences.INTERVAL_FIFTEEN_MINUTES.toString();
- hourly = this.state.interval === Preferences.INTERVAL_HOUR.toString();
-
- sendImmediatleyValue = Preferences.INTERVAL_IMMEDIATE.toString();
- }
-
- return (
-
- {sendEmailNotifications &&
-
-
-
- {emailBatchingEnabled &&
-
-
-
-
-
-
- }
-
-
- }
- {!sendEmailNotifications &&
-
- }
-
- );
- };
-
- buildReplySection = () => {
- const {theme} = this.props;
- const style = getStyleSheet(theme);
-
- return (
-
- );
- };
-
- buildMobilePushSection = () => {
- const {config, theme} = this.props;
- const style = getStyleSheet(theme);
-
- const pushNotificationsEnabled = config.SendPushNotifications === 'true';
- if (!pushNotificationsEnabled) {
- return null;
- }
-
- return (
-
- );
- };
-
- buildMobilePushStatusSection = () => {
- const {config, theme} = this.props;
- const style = getStyleSheet(theme);
-
- const showSection = config.SendPushNotifications === 'true' && this.state.push !== 'none';
- if (!showSection) {
- return null;
- }
-
- return (
-
- );
- };
-
- close = () => {
- this.props.navigator.pop({animated: true});
- };
-
- render() {
- const {theme} = this.props;
- const style = getStyleSheet(theme);
-
- if (this.state.saving) {
- return (
-
-
-
-
- );
- }
-
- return (
-
-
-
- {this.buildMentionSection()}
- {this.buildEmailSection()}
- {this.buildReplySection()}
- {this.buildMobilePushSection()}
- {this.buildMobilePushStatusSection()}
-
-
- );
- }
-}
-
-const getStyleSheet = makeStyleSheetFromTheme((theme) => {
- return {
- wrapper: {
- flex: 1,
- backgroundColor: theme.centerChannelBg
- },
- input: {
- color: theme.centerChannelColor,
- fontSize: 12,
- height: 40
- },
- separator: {
- height: 1,
- flex: 1,
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
- marginHorizontal: 15
- },
- scrollView: {
- flex: 1,
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.03)
- },
- scrollViewContent: {
- paddingBottom: 30
- }
- };
-});
-
-export default injectIntl(AccountNotifications);
diff --git a/app/screens/account_settings/account_settings.js b/app/screens/account_settings/account_settings.js
deleted file mode 100644
index fb9767165..000000000
--- a/app/screens/account_settings/account_settings.js
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
-import {injectIntl, intlShape} from 'react-intl';
-import {
- TouchableOpacity,
- View
-} from 'react-native';
-import Icon from 'react-native-vector-icons/FontAwesome';
-
-import FormattedText from 'app/components/formatted_text';
-import StatusBar from 'app/components/status_bar';
-import {preventDoubleTap} from 'app/utils/tap';
-import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
-
-class AccountSettings extends PureComponent {
- static propTypes = {
- intl: intlShape.isRequired,
- navigator: PropTypes.object,
- theme: PropTypes.object.isRequired
- };
-
- // The enabled arg can be removed once all the scenes have been implemented.
- buildItemRow = (icon, id, defaultMessage, action, separator = true, enabled = false) => {
- if (!enabled) {
- return null;
- }
-
- const {theme} = this.props;
- const style = getStyleSheet(theme);
-
- return (
-
- preventDoubleTap(action)}
- >
-
-
-
-
-
-
- {separator && }
-
- );
- };
-
- goToAccountNotifications = () => {
- const {intl, navigator, theme} = this.props;
- navigator.push({
- backButtonTitle: '',
- screen: 'AccountNotifications',
- title: intl.formatMessage({id: 'user.settings.modal.notifications', defaultMessage: 'Notifications'}),
- animated: true,
- navigatorStyle: {
- navBarTextColor: theme.sidebarHeaderTextColor,
- navBarBackgroundColor: theme.sidebarHeaderBg,
- navBarButtonColor: theme.sidebarHeaderTextColor,
- screenBackgroundColor: theme.centerChannelBg
- }
- });
- };
-
- renderItems = () => {
- return [
- this.buildItemRow('gear', 'user.settings.modal.general', 'General', () => true, true, false),
- this.buildItemRow('lock', 'user.settings.modal.security', 'Security', () => true, true, false),
- this.buildItemRow('bell', 'user.settings.modal.notifications', 'Notifications', this.goToAccountNotifications, false, true),
- this.buildItemRow('mobile', 'user.settings.modal.display', 'Display', () => true, true, false),
- this.buildItemRow('wrench', 'user.settings.modal.advanced', 'Advanced', () => true, false, false)
- ];
- };
-
- render() {
- const {theme} = this.props;
- const style = getStyleSheet(theme);
-
- return (
-
-
-
-
- {this.renderItems()}
-
-
-
- );
- }
-}
-
-const getStyleSheet = makeStyleSheetFromTheme((theme) => {
- return {
- container: {
- flex: 1,
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.03)
- },
- item: {
- height: 45,
- flexDirection: 'row',
- alignItems: 'center'
- },
- itemLeftIcon: {
- color: changeOpacity(theme.centerChannelColor, 0.5)
- },
- itemLeftIconContainer: {
- width: 18,
- marginRight: 15,
- alignItems: 'center',
- justifyContent: 'center'
- },
- itemText: {
- fontSize: 16,
- color: theme.centerChannelColor,
- flex: 1
- },
- itemRightIcon: {
- color: changeOpacity(theme.centerChannelColor, 0.5)
- },
- itemsContainer: {
- marginTop: 30,
- backgroundColor: theme.centerChannelBg,
- borderTopWidth: 1,
- borderBottomWidth: 1,
- borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
- borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1)
- },
- itemWrapper: {
- marginHorizontal: 15
- },
- separator: {
- height: 1,
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.1)
- },
- wrapper: {
- flex: 1,
- backgroundColor: theme.centerChannelBg
- }
- };
-});
-
-export default injectIntl(AccountSettings);
diff --git a/app/screens/advanced_settings/advanced_settings.js b/app/screens/advanced_settings/advanced_settings.js
deleted file mode 100644
index e8d236a68..000000000
--- a/app/screens/advanced_settings/advanced_settings.js
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
-import {injectIntl, intlShape} from 'react-intl';
-import {
- Alert,
- TouchableOpacity,
- View
-} from 'react-native';
-import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
-
-import FormattedText from 'app/components/formatted_text';
-import StatusBar from 'app/components/status_bar';
-import {preventDoubleTap} from 'app/utils/tap';
-import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
-
-class AdvancedSettings extends PureComponent {
- static propTypes = {
- actions: PropTypes.shape({
- purgeOfflineStore: PropTypes.func.isRequired
- }).isRequired,
- intl: intlShape.isRequired,
- theme: PropTypes.object
- };
-
- buildItemRow = (icon, id, defaultMessage, action, separator = true, nextArrow = false) => {
- const {theme} = this.props;
- const style = getStyleSheet(theme);
-
- return (
-
- this.handlePress(action)}
- >
-
-
-
-
- {nextArrow &&
-
- }
-
- {separator && }
-
- );
- };
-
- clearOfflineCache = () => {
- const {actions, intl} = this.props;
-
- Alert.alert(
- intl.formatMessage({id: 'mobile.advanced_settings.reset_title', defaultMessage: 'Reset Cache'}),
- intl.formatMessage({id: 'mobile.advanced_settings.reset_message', defaultMessage: '\nThis will reset all offline data and restart the app. You will be automatically logged back in once the app restarts.\n'}),
- [{
- text: intl.formatMessage({id: 'mobile.advanced_settings.reset_button', defaultMessage: 'Reset'}),
- onPress: () => actions.purgeOfflineStore()
- }, {
- text: intl.formatMessage({id: 'channel_modal.cancel', defaultMessage: 'Cancel'}),
- onPress: () => true
- }]
- );
- };
-
- handlePress = (action) => {
- preventDoubleTap(action, this);
- };
-
- renderItems = () => {
- return [
- this.buildItemRow('storage', 'mobile.advanced_settings.reset_title', 'Reset Cache', this.clearOfflineCache, false, false)
- ];
- };
-
- render() {
- const {theme} = this.props;
- const style = getStyleSheet(theme);
-
- return (
-
-
-
-
- {this.renderItems()}
-
-
-
- );
- }
-}
-
-const getStyleSheet = makeStyleSheetFromTheme((theme) => {
- return {
- container: {
- flex: 1,
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.03)
- },
- item: {
- height: 45,
- flexDirection: 'row',
- alignItems: 'center'
- },
- itemLeftIcon: {
- color: changeOpacity(theme.centerChannelColor, 0.5)
- },
- itemLeftIconContainer: {
- width: 18,
- marginRight: 15,
- alignItems: 'center',
- justifyContent: 'center'
- },
- itemText: {
- fontSize: 16,
- color: theme.centerChannelColor,
- flex: 1
- },
- itemRightIcon: {
- color: changeOpacity(theme.centerChannelColor, 0.5)
- },
- itemsContainer: {
- marginTop: 30,
- backgroundColor: theme.centerChannelBg,
- borderTopWidth: 1,
- borderBottomWidth: 1,
- borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
- borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1)
- },
- itemWrapper: {
- marginHorizontal: 15
- },
- separator: {
- height: 1,
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.1)
- },
- wrapper: {
- flex: 1,
- backgroundColor: theme.centerChannelBg
- }
- };
-});
-
-export default injectIntl(AdvancedSettings);
diff --git a/app/screens/index.js b/app/screens/index.js
index 574c739a2..325d8a446 100644
--- a/app/screens/index.js
+++ b/app/screens/index.js
@@ -5,10 +5,9 @@ import React from 'react';
import {Navigation} from 'react-native-navigation';
import About from 'app/screens/about';
-import AccountSettings from 'app/screens/account_settings';
-import AccountNotifications from 'app/screens/account_notifications';
+
import AddReaction from 'app/screens/add_reaction';
-import AdvancedSettings from 'app/screens/advanced_settings';
+import AdvancedSettings from 'app/screens/settings/advanced_settings';
import Channel from 'app/screens/channel';
import ChannelAddMembers from 'app/screens/channel_add_members';
import ChannelInfo from 'app/screens/channel_info';
@@ -24,13 +23,18 @@ import Mfa from 'app/screens/mfa';
import MoreChannels from 'app/screens/more_channels';
import MoreDirectMessages from 'app/screens/more_dms';
import Notification from 'app/screens/notification';
+import NotificationSettings from 'app/screens/settings/notification_settings';
+import NotificationSettingsEmail from 'app/screens/settings/notification_settings_email';
+import NotificationSettingsMentions from 'app/screens/settings/notification_settings_mentions';
+import NotificationSettingsMentionsKeywords from 'app/screens/settings/notification_settings_mentions_keywords';
+import NotificationSettingsMobile from 'app/screens/settings/notification_settings_mobile';
import OptionsModal from 'app/screens/options_modal';
import Root from 'app/screens/root';
import SSO from 'app/screens/sso';
import Search from 'app/screens/search';
import SelectServer from 'app/screens/select_server';
import SelectTeam from 'app/screens/select_team';
-import Settings from 'app/screens/settings';
+import Settings from 'app/screens/settings/general';
import Thread from 'app/screens/thread';
import UserProfile from 'app/screens/user_profile';
@@ -52,8 +56,6 @@ function wrapWithContextProvider(Comp, excludeEvents = false) {
export function registerScreens(store, Provider) {
Navigation.registerComponent('About', () => wrapWithContextProvider(About), store, Provider);
- Navigation.registerComponent('AccountSettings', () => wrapWithContextProvider(AccountSettings), store, Provider);
- Navigation.registerComponent('AccountNotifications', () => wrapWithContextProvider(AccountNotifications), store, Provider);
Navigation.registerComponent('AddReaction', () => wrapWithContextProvider(AddReaction), store, Provider);
Navigation.registerComponent('AdvancedSettings', () => wrapWithContextProvider(AdvancedSettings), store, Provider);
Navigation.registerComponent('Channel', () => wrapWithContextProvider(Channel), store, Provider);
@@ -72,6 +74,11 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('MoreDirectMessages', () => wrapWithContextProvider(MoreDirectMessages), store, Provider);
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(OptionsModal), store, Provider);
Navigation.registerComponent('Notification', () => wrapWithContextProvider(Notification, true), store, Provider);
+ Navigation.registerComponent('NotificationSettings', () => wrapWithContextProvider(NotificationSettings), store, Provider);
+ Navigation.registerComponent('NotificationSettingsEmail', () => wrapWithContextProvider(NotificationSettingsEmail), store, Provider);
+ Navigation.registerComponent('NotificationSettingsMentions', () => wrapWithContextProvider(NotificationSettingsMentions), store, Provider);
+ Navigation.registerComponent('NotificationSettingsMentionsKeywords', () => wrapWithContextProvider(NotificationSettingsMentionsKeywords), store, Provider);
+ Navigation.registerComponent('NotificationSettingsMobile', () => wrapWithContextProvider(NotificationSettingsMobile), store, Provider);
Navigation.registerComponent('Root', () => Root, store, Provider);
Navigation.registerComponent('Search', () => wrapWithContextProvider(Search), store, Provider);
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(SelectServer), store, Provider);
diff --git a/app/screens/settings/advanced_settings/advanced_settings.js b/app/screens/settings/advanced_settings/advanced_settings.js
new file mode 100644
index 000000000..5764e12f2
--- /dev/null
+++ b/app/screens/settings/advanced_settings/advanced_settings.js
@@ -0,0 +1,95 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {injectIntl, intlShape} from 'react-intl';
+import {
+ Alert,
+ Platform,
+ View
+} from 'react-native';
+
+import SettingsItem from 'app/screens/settings/settings_item';
+import StatusBar from 'app/components/status_bar';
+import {preventDoubleTap} from 'app/utils/tap';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+class AdvancedSettings extends PureComponent {
+ static propTypes = {
+ actions: PropTypes.shape({
+ purgeOfflineStore: PropTypes.func.isRequired
+ }).isRequired,
+ intl: intlShape.isRequired,
+ theme: PropTypes.object
+ };
+
+ clearOfflineCache = () => {
+ const {actions, intl} = this.props;
+
+ Alert.alert(
+ intl.formatMessage({id: 'mobile.advanced_settings.reset_title', defaultMessage: 'Reset Cache'}),
+ intl.formatMessage({id: 'mobile.advanced_settings.reset_message', defaultMessage: '\nThis will reset all offline data and restart the app. You will be automatically logged back in once the app restarts.\n'}),
+ [{
+ text: intl.formatMessage({id: 'mobile.advanced_settings.reset_button', defaultMessage: 'Reset'}),
+ onPress: () => actions.purgeOfflineStore()
+ }, {
+ text: intl.formatMessage({id: 'channel_modal.cancel', defaultMessage: 'Cancel'}),
+ onPress: () => true
+ }]
+ );
+ };
+
+ handlePress = (action) => {
+ preventDoubleTap(action, this);
+ };
+
+ render() {
+ const {theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return (
+
+
+
+
+ this.handlePress(this.clearOfflineCache)}
+ separator={false}
+ showArrow={false}
+ theme={theme}
+ />
+
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ },
+ wrapper: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
+ flex: 1,
+ ...Platform.select({
+ ios: {
+ paddingTop: 35
+ }
+ })
+ },
+ divider: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ height: 1
+ }
+ };
+});
+
+export default injectIntl(AdvancedSettings);
diff --git a/app/screens/advanced_settings/index.js b/app/screens/settings/advanced_settings/index.js
similarity index 100%
rename from app/screens/advanced_settings/index.js
rename to app/screens/settings/advanced_settings/index.js
diff --git a/app/screens/settings/index.js b/app/screens/settings/general/index.js
similarity index 100%
rename from app/screens/settings/index.js
rename to app/screens/settings/general/index.js
diff --git a/app/screens/settings/settings.js b/app/screens/settings/general/settings.js
similarity index 81%
rename from app/screens/settings/settings.js
rename to app/screens/settings/general/settings.js
index 0242339b2..de18ad725 100644
--- a/app/screens/settings/settings.js
+++ b/app/screens/settings/general/settings.js
@@ -3,7 +3,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {injectIntl, intlShape} from 'react-intl';
+import {intlShape, injectIntl} from 'react-intl';
import {
InteractionManager,
Linking,
@@ -12,13 +12,12 @@ import {
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
+import SettingsItem from 'app/screens/settings/settings_item';
import StatusBar from 'app/components/status_bar';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {isValidUrl} from 'app/utils/url';
-import SettingsItem from './settings_item';
-
class Settings extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
@@ -82,13 +81,13 @@ class Settings extends PureComponent {
});
};
- goToAccountSettings = () => {
+ goToNotifications = () => {
const {intl, navigator, theme} = this.props;
navigator.push({
- screen: 'AccountSettings',
- title: intl.formatMessage({id: 'user.settings.modal.title', defaultMessage: 'Account Settings'}),
- animated: true,
+ screen: 'NotificationSettings',
backButtonTitle: '',
+ title: intl.formatMessage({id: 'user.settings.modal.notifications', defaultMessage: 'Notifications'}),
+ animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
@@ -174,28 +173,30 @@ class Settings extends PureComponent {
const style = getStyleSheet(theme);
const showTeams = Object.keys(joinableTeams).length > 0;
const showHelp = isValidUrl(config.HelpLink);
+ const showArrow = Platform.OS === 'ios';
return (
+
this.handlePress(this.goToAccountSettings)}
- separator={true}
+ defaultMessage='Notifications'
+ i18nId='user.settings.modal.notifications'
+ iconName='ios-notifications'
+ iconType='ion'
+ onPress={() => this.handlePress(this.goToNotifications)}
+ showArrow={showArrow}
theme={theme}
/>
{showTeams &&
this.handlePress(this.goToSelectTeam)}
- separator={true}
+ showArrow={showArrow}
theme={theme}
/>
}
@@ -203,54 +204,56 @@ class Settings extends PureComponent {
preventDoubleTap(this.openHelp, this)}
- separator={true}
+ iconName='md-help'
+ iconType='ion'
+ onPress={() => this.handlePress(this.openHelp)}
+ showArrow={showArrow}
theme={theme}
/>
}
this.handlePress(this.openErrorEmail)}
- separator={true}
+ showArrow={showArrow}
theme={theme}
/>
this.handlePress(this.goToAdvancedSettings)}
- separator={true}
+ showArrow={showArrow}
theme={theme}
/>
this.handlePress(this.goToAbout)}
separator={false}
+ showArrow={showArrow}
theme={theme}
/>
-
-
-
- this.handlePress(this.logout)}
- separator={false}
- theme={theme}
- />
+
+
+ this.handlePress(this.logout)}
+ separator={false}
+ showArrow={false}
+ theme={theme}
+ />
+
+
);
@@ -259,32 +262,25 @@ class Settings extends PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
- leftComponent: {
- alignItems: 'center',
- flex: 1,
- justifyContent: 'center',
- width: 44
- },
container: {
flex: 1,
backgroundColor: theme.centerChannelBg
},
wrapper: {
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
- flex: 1
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
+ flex: 1,
+ ...Platform.select({
+ ios: {
+ paddingTop: 35
+ }
+ })
},
divider: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
height: 1
},
footer: {
- height: 51,
- justifyContent: 'flex-end',
- ...Platform.select({
- android: {
- marginBottom: 20
- }
- })
+ marginTop: 35
}
};
});
diff --git a/app/screens/account_notifications/index.js b/app/screens/settings/notification_settings/index.js
similarity index 75%
rename from app/screens/account_notifications/index.js
rename to app/screens/settings/notification_settings/index.js
index 0b6e58fc5..8b7066008 100644
--- a/app/screens/account_notifications/index.js
+++ b/app/screens/settings/notification_settings/index.js
@@ -5,18 +5,19 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
+import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences';
import {handleUpdateUserNotifyProps} from 'app/actions/views/account_notifications';
import {getTheme} from 'app/selectors/preferences';
-import AccountNotifications from './account_notifications';
+import NotificationSettings from './notification_settings';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
config: state.entities.general.config,
- myPreferences: state.entities.preferences.myPreferences,
currentUser: getCurrentUser(state),
+ myPreferences: getMyPreferences(state),
updateMeRequest: state.requests.users.updateMe,
theme: getTheme(state)
};
@@ -30,4 +31,4 @@ function mapDispatchToProps(dispatch) {
};
}
-export default connect(mapStateToProps, mapDispatchToProps)(AccountNotifications);
+export default connect(mapStateToProps, mapDispatchToProps)(NotificationSettings);
diff --git a/app/screens/settings/notification_settings/notification_settings.js b/app/screens/settings/notification_settings/notification_settings.js
new file mode 100644
index 000000000..d8703d82f
--- /dev/null
+++ b/app/screens/settings/notification_settings/notification_settings.js
@@ -0,0 +1,449 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {injectIntl, intlShape} from 'react-intl';
+import {
+ Alert,
+ Modal,
+ Platform,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import deepEqual from 'deep-equal';
+
+import {Preferences, RequestStatus} from 'mattermost-redux/constants';
+import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
+
+import FormattedText from 'app/components/formatted_text';
+import {RadioButton, RadioButtonGroup} from 'app/components/radio_button';
+import StatusBar from 'app/components/status_bar';
+import SettingsItem from 'app/screens/settings/settings_item';
+import {preventDoubleTap} from 'app/utils/tap';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+class NotificationSettings extends PureComponent {
+ static propTypes = {
+ actions: PropTypes.shape({
+ handleUpdateUserNotifyProps: PropTypes.func.isRequired
+ }),
+ config: PropTypes.object.isRequired,
+ currentUser: PropTypes.object.isRequired,
+ intl: intlShape.isRequired,
+ myPreferences: PropTypes.object.isRequired,
+ navigator: PropTypes.object,
+ theme: PropTypes.object.isRequired,
+ updateMeRequest: PropTypes.object.isRequired
+ };
+
+ state = {
+ showEmailNotificationsModal: false
+ };
+
+ componentWillReceiveProps(nextProps) {
+ const {updateMeRequest, intl} = nextProps;
+ if (this.props.updateMeRequest !== updateMeRequest && updateMeRequest.status === RequestStatus.FAILURE) {
+ Alert.alert(
+ intl.formatMessage({
+ id: 'mobile.notification_settings.save_failed_title',
+ defaultMessage: 'Connection issue'
+ }),
+ intl.formatMessage({
+ id: 'mobile.notification_settings.save_failed_description',
+ defaultMessage: 'The notification settings failed to save due to a connection issue, please try again.'
+ })
+ );
+ }
+ }
+
+ handlePress = (action) => {
+ preventDoubleTap(action, this);
+ };
+
+ goToNotificationSettingsEmail = () => {
+ if (Platform.OS === 'ios') {
+ const {currentUser, intl, navigator, theme} = this.props;
+ navigator.push({
+ backButtonTitle: '',
+ screen: 'NotificationSettingsEmail',
+ title: intl.formatMessage({
+ id: 'mobile.notification_settings.email_title',
+ defaultMessage: 'Email Notifications'
+ }),
+ animated: true,
+ navigatorStyle: {
+ navBarTextColor: theme.sidebarHeaderTextColor,
+ navBarBackgroundColor: theme.sidebarHeaderBg,
+ navBarButtonColor: theme.sidebarHeaderTextColor,
+ screenBackgroundColor: theme.centerChannelBg
+ },
+ passProps: {
+ currentUser,
+ onBack: this.saveNotificationProps
+ }
+ });
+ } else {
+ this.setState({showEmailNotificationsModal: true});
+ }
+ };
+
+ goToNotificationSettingsMentions = () => {
+ const {currentUser, intl, navigator, theme} = this.props;
+ navigator.push({
+ backButtonTitle: '',
+ screen: 'NotificationSettingsMentions',
+ title: intl.formatMessage({id: 'mobile.notification_settings.mentions_replies', defaultMessage: 'Mentions and Replies'}),
+ animated: true,
+ navigatorStyle: {
+ navBarTextColor: theme.sidebarHeaderTextColor,
+ navBarBackgroundColor: theme.sidebarHeaderBg,
+ navBarButtonColor: theme.sidebarHeaderTextColor,
+ screenBackgroundColor: theme.centerChannelBg
+ },
+ passProps: {
+ currentUser,
+ onBack: this.saveNotificationProps
+ }
+ });
+ };
+
+ goToNotificationSettingsMobile = () => {
+ const {currentUser, intl, navigator, theme} = this.props;
+ navigator.push({
+ backButtonTitle: '',
+ screen: 'NotificationSettingsMobile',
+ title: intl.formatMessage({id: 'mobile.notification_settings.mobile_title', defaultMessage: 'Mobile Notifications'}),
+ animated: true,
+ navigatorStyle: {
+ navBarTextColor: theme.sidebarHeaderTextColor,
+ navBarBackgroundColor: theme.sidebarHeaderBg,
+ navBarButtonColor: theme.sidebarHeaderTextColor,
+ screenBackgroundColor: theme.centerChannelBg
+ },
+ passProps: {
+ currentUser,
+ onBack: this.saveNotificationProps
+ }
+ });
+ };
+
+ setEmailNotifications = (emailSetting) => {
+ this.setState({emailSetting});
+ };
+
+ saveEmailNotifications = () => {
+ const {config, currentUser} = this.props;
+ const {emailSetting} = this.state;
+
+ this.setState({showEmailNotificationsModal: false});
+
+ if (emailSetting) {
+ let email = emailSetting;
+ let interval;
+
+ const emailBatchingEnabled = config.EnableEmailBatching === 'true';
+ if (emailBatchingEnabled && emailSetting !== 'false') {
+ interval = emailSetting;
+ email = 'true';
+ }
+
+ this.saveNotificationProps({
+ ...currentUser.notify_props,
+ email,
+ interval
+ });
+ }
+ };
+
+ saveNotificationProps = (notifyProps) => {
+ const {currentUser} = this.props;
+ const {user_id} = notifyProps;
+ const previousProps = {
+ ...currentUser.notify_props,
+ user_id
+ };
+
+ if (notifyProps.interval) {
+ previousProps.interval = notifyProps.interval;
+ }
+
+ if (!deepEqual(previousProps, notifyProps)) {
+ this.props.actions.handleUpdateUserNotifyProps(notifyProps);
+ }
+ };
+
+ renderEmailNotificationSettings = (style) => {
+ if (Platform.OS === 'ios') {
+ return null;
+ }
+
+ const {config, currentUser, intl, myPreferences} = this.props;
+ const notifyProps = currentUser.notify_props || {};
+ const sendEmailNotifications = config.SendEmailNotifications === 'true';
+ const emailBatchingEnabled = sendEmailNotifications && config.EnableEmailBatching === 'true';
+
+ const never = notifyProps.email === 'false';
+ let sendImmediatley = notifyProps.email === 'true';
+ let sendImmediatleyValue = 'true';
+ let fifteenMinutes;
+ let hourly;
+ let interval;
+
+ if (emailBatchingEnabled) {
+ const emailPreferences = getPreferencesByCategory(myPreferences, Preferences.CATEGORY_NOTIFICATIONS);
+ if (emailPreferences.size) {
+ interval = emailPreferences.get(Preferences.EMAIL_INTERVAL).value;
+ }
+ }
+
+ if (emailBatchingEnabled && notifyProps.email !== 'false') {
+ sendImmediatley = interval === Preferences.INTERVAL_IMMEDIATE.toString();
+ fifteenMinutes = interval === Preferences.INTERVAL_FIFTEEN_MINUTES.toString();
+ hourly = interval === Preferences.INTERVAL_HOUR.toString();
+ sendImmediatleyValue = Preferences.INTERVAL_IMMEDIATE.toString();
+ }
+
+ let helpText;
+ if (sendEmailNotifications) {
+ helpText = (
+
+ );
+ }
+
+ return (
+ this.setState({showEmailNotificationsModal: false})}
+ >
+
+
+
+
+
+
+ {!sendEmailNotifications &&
+
+ }
+ {sendEmailNotifications &&
+
+
+ {emailBatchingEnabled &&
+
+ }
+ {emailBatchingEnabled &&
+
+ }
+
+
+ }
+ {helpText}
+
+
+
+
+
+
+
+ {sendEmailNotifications &&
+
+
+
+
+
+
+ }
+
+
+
+
+
+ );
+ };
+
+ render() {
+ const {theme} = this.props;
+ const style = getStyleSheet(theme);
+ const showArrow = Platform.OS === 'ios';
+
+ return (
+
+
+
+
+ this.handlePress(this.goToNotificationSettingsMentions)}
+ separator={true}
+ showArrow={showArrow}
+ theme={theme}
+ />
+ this.handlePress(this.goToNotificationSettingsMobile)}
+ separator={true}
+ showArrow={showArrow}
+ theme={theme}
+ />
+ this.handlePress(this.goToNotificationSettingsEmail)}
+ separator={false}
+ showArrow={showArrow}
+ theme={theme}
+ />
+
+
+ {this.renderEmailNotificationSettings(style)}
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ },
+ wrapper: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
+ flex: 1,
+ ...Platform.select({
+ ios: {
+ paddingTop: 35
+ }
+ })
+ },
+ divider: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ height: 1,
+ width: '100%'
+ },
+ modalOverlay: {
+ backgroundColor: changeOpacity('#000000', 0.6),
+ alignItems: 'center',
+ flex: 1
+ },
+ modal: {
+ backgroundColor: theme.centerChannelBg,
+ borderRadius: 4,
+ marginTop: 20,
+ width: '95%'
+ },
+ modalBody: {
+ paddingHorizontal: 24
+ },
+ modalTitleContainer: {
+ marginBottom: 30,
+ marginTop: 20
+ },
+ modalTitle: {
+ color: theme.centerChannelColor,
+ fontSize: 19
+ },
+ modalOptionDisabled: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ fontSize: 17
+ },
+ modalHelpText: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ fontSize: 13,
+ marginTop: 20
+ },
+ modalFooter: {
+ alignItems: 'flex-end',
+ height: 58,
+ marginTop: 40,
+ width: '100%'
+ },
+ modalFooterContainer: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ paddingRight: 24
+ },
+ modalFooterOptionContainer: {
+ alignItems: 'center',
+ height: 40,
+ justifyContent: 'center',
+ paddingHorizontal: 10,
+ paddingVertical: 5
+ },
+ modalFooterOption: {
+ color: theme.linkColor,
+ fontSize: 14
+ }
+ };
+});
+
+export default injectIntl(NotificationSettings);
diff --git a/app/screens/settings/notification_settings_email/index.js b/app/screens/settings/notification_settings_email/index.js
new file mode 100644
index 000000000..0b490e3eb
--- /dev/null
+++ b/app/screens/settings/notification_settings_email/index.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getConfig} from 'mattermost-redux/selectors/entities/general';
+import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences';
+
+import {getTheme} from 'app/selectors/preferences';
+
+import NotificationSettingsEmail from './notification_settings_email';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps,
+ config: getConfig(state),
+ myPreferences: getMyPreferences(state),
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(NotificationSettingsEmail);
diff --git a/app/screens/settings/notification_settings_email/notification_settings_email.js b/app/screens/settings/notification_settings_email/notification_settings_email.js
new file mode 100644
index 000000000..512ac27de
--- /dev/null
+++ b/app/screens/settings/notification_settings_email/notification_settings_email.js
@@ -0,0 +1,249 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ ScrollView,
+ View
+} from 'react-native';
+
+import {Preferences} from 'mattermost-redux/constants';
+import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
+
+import FormattedText from 'app/components/formatted_text';
+import StatusBar from 'app/components/status_bar';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+import Section from 'app/screens/settings/section';
+import SectionItem from 'app/screens/settings/section_item';
+
+export default class NotificationSettingsEmail extends PureComponent {
+ static propTypes = {
+ config: PropTypes.object.isRequired,
+ currentUser: PropTypes.object.isRequired,
+ myPreferences: PropTypes.object.isRequired,
+ navigator: PropTypes.object,
+ onBack: PropTypes.func.isRequired,
+ theme: PropTypes.object.isRequired
+ };
+
+ constructor(props) {
+ super(props);
+
+ const {currentUser} = props;
+ const notifyProps = currentUser.notify_props || {};
+
+ props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
+ this.setStateFromNotifyProps(notifyProps);
+ }
+
+ onNavigatorEvent = (event) => {
+ if (event.type === 'ScreenChangedEvent') {
+ switch (event.id) {
+ case 'willDisappear':
+ this.saveUserNotifyProps();
+ break;
+ }
+ }
+ };
+
+ setEmailNotifications = (value) => {
+ const {config} = this.props;
+ let email = value;
+ let interval;
+
+ const emailBatchingEnabled = config.EnableEmailBatching === 'true';
+ if (emailBatchingEnabled && value !== 'false') {
+ interval = value;
+ email = 'true';
+ }
+
+ this.setState({
+ email,
+ interval
+ });
+ };
+
+ setStateFromNotifyProps = (notifyProps) => {
+ const {config, myPreferences} = this.props;
+ let interval;
+ if (config.SendEmailNotifications === 'true' && config.EnableEmailBatching === 'true') {
+ const emailPreferences = getPreferencesByCategory(myPreferences, Preferences.CATEGORY_NOTIFICATIONS);
+ if (emailPreferences.size) {
+ interval = emailPreferences.get(Preferences.EMAIL_INTERVAL).value;
+ }
+ }
+
+ const newState = {
+ ...notifyProps,
+ interval
+ };
+
+ this.state = {...newState};
+ };
+
+ saveUserNotifyProps = () => {
+ this.props.onBack({
+ ...this.state,
+ user_id: this.props.currentUser.id
+ });
+ };
+
+ renderEmailSection = () => {
+ const {config, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ const sendEmailNotifications = config.SendEmailNotifications === 'true';
+ const emailBatchingEnabled = sendEmailNotifications && config.EnableEmailBatching === 'true';
+
+ let sendImmediatley = this.state.email === 'true';
+ let sendImmediatleyValue = 'true';
+ let fifteenMinutes;
+ let hourly;
+ const never = this.state.email === 'false';
+
+ if (emailBatchingEnabled && this.state.email !== 'false') {
+ sendImmediatley = this.state.interval === Preferences.INTERVAL_IMMEDIATE.toString();
+ fifteenMinutes = this.state.interval === Preferences.INTERVAL_FIFTEEN_MINUTES.toString();
+ hourly = this.state.interval === Preferences.INTERVAL_HOUR.toString();
+
+ sendImmediatleyValue = Preferences.INTERVAL_IMMEDIATE.toString();
+ }
+
+ return (
+
+ {sendEmailNotifications &&
+
+
+ )}
+ action={this.setEmailNotifications}
+ actionType='select'
+ actionValue={sendImmediatleyValue}
+ selected={sendImmediatley}
+ theme={theme}
+ />
+
+ {emailBatchingEnabled &&
+
+
+ )}
+ action={this.setEmailNotifications}
+ actionType='select'
+ actionValue={Preferences.INTERVAL_FIFTEEN_MINUTES.toString()}
+ selected={fifteenMinutes}
+ theme={theme}
+ />
+
+
+ )}
+ action={this.setEmailNotifications}
+ actionType='select'
+ actionValue={Preferences.INTERVAL_HOUR.toString()}
+ selected={hourly}
+ theme={theme}
+ />
+
+
+ }
+
+ )}
+ action={this.setEmailNotifications}
+ actionType='select'
+ actionValue='false'
+ selected={never}
+ theme={theme}
+ />
+
+ }
+ {!sendEmailNotifications &&
+
+ }
+
+ );
+ };
+
+ render() {
+ const {theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return (
+
+
+
+ {this.renderEmailSection()}
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ },
+ input: {
+ color: theme.centerChannelColor,
+ fontSize: 12,
+ height: 40
+ },
+ separator: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ flex: 1,
+ height: 1,
+ marginLeft: 15
+ },
+ scrollView: {
+ flex: 1,
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06)
+ },
+ scrollViewContent: {
+ paddingVertical: 35
+ },
+ disabled: {
+ color: theme.centerChannelColor,
+ fontSize: 15,
+ paddingHorizontal: 15,
+ paddingVertical: 10
+ }
+ };
+});
diff --git a/app/screens/account_settings/index.js b/app/screens/settings/notification_settings_mentions/index.js
similarity index 54%
rename from app/screens/account_settings/index.js
rename to app/screens/settings/notification_settings_mentions/index.js
index adf8be570..e823f0239 100644
--- a/app/screens/account_settings/index.js
+++ b/app/screens/settings/notification_settings_mentions/index.js
@@ -5,12 +5,13 @@ import {connect} from 'react-redux';
import {getTheme} from 'app/selectors/preferences';
-import AccountSettings from './account_settings';
+import NotificationSettingsMentions from './notification_settings_mentions';
-function mapStateToProps(state) {
+function mapStateToProps(state, ownProps) {
return {
+ ...ownProps,
theme: getTheme(state)
};
}
-export default connect(mapStateToProps)(AccountSettings);
+export default connect(mapStateToProps)(NotificationSettingsMentions);
diff --git a/app/screens/settings/notification_settings_mentions/notification_settings_mention_base.js b/app/screens/settings/notification_settings_mentions/notification_settings_mention_base.js
new file mode 100644
index 000000000..5dbd99385
--- /dev/null
+++ b/app/screens/settings/notification_settings_mentions/notification_settings_mention_base.js
@@ -0,0 +1,118 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {intlShape} from 'react-intl';
+
+export default class NotificationSettingsMentionsBase extends PureComponent {
+ static propTypes = {
+ currentUser: PropTypes.object.isRequired,
+ intl: intlShape.isRequired,
+ navigator: PropTypes.object,
+ onBack: PropTypes.func.isRequired,
+ theme: PropTypes.object.isRequired
+ };
+
+ constructor(props) {
+ super(props);
+
+ const {currentUser} = props;
+ const notifyProps = currentUser.notify_props || {};
+
+ props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
+
+ this.goingBack = true; //use to identify if the navigator is popping this screen
+ this.setStateFromNotifyProps(notifyProps);
+ }
+
+ onNavigatorEvent = (event) => {
+ if (event.type === 'ScreenChangedEvent' && this.goingBack) {
+ switch (event.id) {
+ case 'willDisappear':
+ this.saveUserNotifyProps();
+ break;
+ }
+ }
+ };
+
+ setStateFromNotifyProps = (notifyProps) => {
+ const mentionKeys = (notifyProps.mention_keys || '').split(',');
+ const usernameMentionIndex = mentionKeys.indexOf(this.props.currentUser.username);
+ if (usernameMentionIndex > -1) {
+ mentionKeys.splice(usernameMentionIndex, 1);
+ }
+
+ const comments = notifyProps.comments || 'never';
+
+ const newState = {
+ ...notifyProps,
+ comments,
+ usernameMention: usernameMentionIndex > -1,
+ mention_keys: mentionKeys.join(','),
+ showKeywordsModal: false,
+ showReplyModal: false
+ };
+
+ this.keywords = newState.mention_keys;
+ this.replyValue = comments;
+ this.state = {...newState};
+ };
+
+ toggleFirstNameMention = () => {
+ this.setState({
+ first_name: (!(this.state.first_name === 'true')).toString()
+ });
+ };
+
+ toggleUsernameMention = () => {
+ this.setState({
+ usernameMention: !this.state.usernameMention
+ });
+ };
+
+ toggleChannelMentions = () => {
+ this.setState({
+ channel: (!(this.state.channel === 'true')).toString()
+ });
+ };
+
+ updateMentionKeys = (text) => {
+ this.goingBack = true;
+ this.setState({
+ mention_keys: text,
+ showKeywordsModal: false
+ });
+ };
+
+ setReplyNotifications = (value) => {
+ this.setState({
+ comments: value,
+ showReplyModal: false
+ });
+ };
+
+ saveUserNotifyProps = () => {
+ let {mention_keys: mentionKeys, usernameMention, ...notifyProps} = this.state; //eslint-disable-line prefer-const
+
+ if (mentionKeys.length > 0) {
+ mentionKeys = mentionKeys.split(',').map((m) => m.replace(/\s/g, ''));
+ } else {
+ mentionKeys = [];
+ }
+
+ if (usernameMention) {
+ mentionKeys.push(`${this.props.currentUser.username}`);
+ }
+
+ mentionKeys = mentionKeys.join(',');
+ Reflect.deleteProperty(notifyProps, 'showKeywordsModal');
+ Reflect.deleteProperty(notifyProps, 'showReplyModal');
+
+ this.props.onBack({
+ ...notifyProps,
+ mention_keys: mentionKeys,
+ user_id: this.props.currentUser.id
+ });
+ };
+}
diff --git a/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js
new file mode 100644
index 000000000..6e2a5ec5e
--- /dev/null
+++ b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js
@@ -0,0 +1,445 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import {
+ Modal,
+ ScrollView,
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import {injectIntl} from 'react-intl';
+
+import FormattedText from 'app/components/formatted_text';
+import {RadioButton, RadioButtonGroup} from 'app/components/radio_button';
+import StatusBar from 'app/components/status_bar';
+import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
+import SectionItem from 'app/screens/settings/section_item';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+import NotificationSettingsMentionsBase from './notification_settings_mention_base';
+
+class NotificationSettingsMentionsIos extends NotificationSettingsMentionsBase {
+ cancelMentionKeys = () => {
+ this.setState({showKeywordsModal: false});
+ this.keywords = this.state.mention_keys;
+ };
+
+ cancelReplyNotification = () => {
+ this.setState({showReplyModal: false});
+ this.replyValue = this.state.comments;
+ };
+
+ onKeywordsChangeText = (value) => {
+ this.keywords = value;
+ };
+
+ onReplyChanged = (value) => {
+ this.replyValue = value;
+ };
+
+ renderKeywordsModal(style) {
+ const {theme} = this.props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ renderReplyModal(style) {
+ const {intl} = this.props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ renderReplySection() {
+ const {theme} = this.props;
+
+ let i18nId;
+ let i18nMessage;
+ switch (this.state.comments) {
+ case 'any':
+ i18nId = 'mobile.account_notifications.threads_start_participate';
+ i18nMessage = 'Threads that I start or participate in';
+ break;
+ case 'root':
+ i18nId = 'mobile.account_notifications.threads_start';
+ i18nMessage = 'Threads that I start';
+ break;
+ case 'never':
+ i18nId = 'mobile.account_notifications.threads_mentions';
+ i18nMessage = 'Mentions in threads';
+ break;
+ }
+
+ return (
+
+ )}
+ label={(
+
+ )}
+ action={this.showReplyModal}
+ actionType='default'
+ theme={theme}
+ />
+ );
+ }
+
+ saveMentionKeys = () => {
+ this.setState({showKeywordsModal: false});
+ this.updateMentionKeys(this.keywords);
+ };
+
+ saveReplyNotification = () => {
+ this.setState({showReplyModal: false});
+ this.setReplyNotifications(this.replyValue);
+ };
+
+ showKeywordsModal = () => {
+ this.setState({showKeywordsModal: true});
+ };
+
+ showReplyModal = () => {
+ this.setState({showReplyModal: true});
+ };
+
+ render() {
+ const {currentUser, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ let mentionKeysComponent;
+ if (this.state.mention_keys) {
+ mentionKeysComponent = ({this.state.mention_keys});
+ } else {
+ mentionKeysComponent = (
+
+ );
+ }
+
+ return (
+
+
+
+ {currentUser.first_name.length > 0 &&
+
+
+ {currentUser.first_name}
+
+ )}
+ description={(
+
+ )}
+ action={this.toggleFirstNameMention}
+ actionType='toggle'
+ selected={this.state.first_name === 'true'}
+ theme={theme}
+ />
+
+
+ }
+
+ {currentUser.username}
+
+ )}
+ description={(
+
+ )}
+ selected={this.state.usernameMention}
+ action={this.toggleUsernameMention}
+ actionType='toggle'
+ theme={theme}
+ />
+
+
+ {'@channel, @all, @here'}
+
+ )}
+ description={(
+
+ )}
+ action={this.toggleChannelMentions}
+ actionType='toggle'
+ selected={this.state.channel === 'true'}
+ theme={theme}
+ />
+
+
+ )}
+ description={mentionKeysComponent}
+ action={this.showKeywordsModal}
+ actionType='default'
+ theme={theme}
+ />
+
+ {this.renderReplySection()}
+
+
+ {this.renderKeywordsModal(style)}
+ {this.renderReplyModal(style)}
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ },
+ input: {
+ color: theme.centerChannelColor,
+ fontSize: 12,
+ height: 40
+ },
+ separator: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ flex: 1,
+ height: 1
+ },
+ scrollView: {
+ flex: 1,
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06)
+ },
+ scrollViewContent: {
+ paddingVertical: 0
+ },
+ modalOverlay: {
+ backgroundColor: changeOpacity('#000000', 0.6),
+ alignItems: 'center',
+ flex: 1
+ },
+ modal: {
+ backgroundColor: theme.centerChannelBg,
+ borderRadius: 4,
+ marginTop: 20,
+ width: '95%'
+ },
+ modalBody: {
+ paddingHorizontal: 24
+ },
+ modalTitleContainer: {
+ marginBottom: 30,
+ marginTop: 20
+ },
+ modalTitle: {
+ color: theme.centerChannelColor,
+ fontSize: 19
+ },
+ modalOptionDisabled: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ fontSize: 17
+ },
+ modalInput: {
+ color: theme.centerChannelColor,
+ fontSize: 19
+ },
+ modalHelpText: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ fontSize: 13,
+ marginTop: 20
+ },
+ modalFooter: {
+ alignItems: 'flex-end',
+ height: 58,
+ marginTop: 40,
+ width: '100%'
+ },
+ modalFooterContainer: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ paddingRight: 24
+ },
+ modalFooterOptionContainer: {
+ alignItems: 'center',
+ height: 40,
+ justifyContent: 'center',
+ paddingHorizontal: 10,
+ paddingVertical: 5
+ },
+ modalFooterOption: {
+ color: theme.linkColor,
+ fontSize: 14
+ }
+ };
+});
+
+export default injectIntl(NotificationSettingsMentionsIos);
diff --git a/app/screens/settings/notification_settings_mentions/notification_settings_mentions.ios.js b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.ios.js
new file mode 100644
index 000000000..05f2c2304
--- /dev/null
+++ b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.ios.js
@@ -0,0 +1,238 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import {
+ ScrollView,
+ Text,
+ View
+} from 'react-native';
+import {injectIntl} from 'react-intl';
+
+import FormattedText from 'app/components/formatted_text';
+import StatusBar from 'app/components/status_bar';
+import Section from 'app/screens/settings/section';
+import SectionItem from 'app/screens/settings/section_item';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+import NotificationSettingsMentionsBase from './notification_settings_mention_base';
+
+class NotificationSettingsMentionsIos extends NotificationSettingsMentionsBase {
+ goToNotificationSettingsMentionKeywords = () => {
+ const {intl, navigator, theme} = this.props;
+ this.goingBack = false;
+
+ navigator.push({
+ backButtonTitle: '',
+ screen: 'NotificationSettingsMentionsKeywords',
+ title: intl.formatMessage({id: 'mobile.notification_settings_mentions.keywords', defaultMessage: 'Keywords'}),
+ animated: true,
+ navigatorStyle: {
+ navBarTextColor: theme.sidebarHeaderTextColor,
+ navBarBackgroundColor: theme.sidebarHeaderBg,
+ navBarButtonColor: theme.sidebarHeaderTextColor,
+ screenBackgroundColor: theme.centerChannelBg
+ },
+ passProps: {
+ keywords: this.state.mention_keys,
+ onBack: this.updateMentionKeys
+ }
+ });
+ };
+
+ renderMentionSection(style) {
+ const {currentUser, theme} = this.props;
+
+ let mentionKeysComponent;
+ if (this.state.mention_keys) {
+ mentionKeysComponent = ({this.state.mention_keys});
+ } else {
+ mentionKeysComponent = (
+
+ );
+ }
+
+ return (
+
+ {currentUser.first_name.length > 0 &&
+
+
+ {currentUser.first_name}
+
+ )}
+ description={(
+
+ )}
+ action={this.toggleFirstNameMention}
+ actionType='toggle'
+ selected={this.state.first_name === 'true'}
+ theme={theme}
+ />
+
+
+ }
+
+ {currentUser.username}
+
+ )}
+ description={(
+
+ )}
+ selected={this.state.usernameMention}
+ action={this.toggleUsernameMention}
+ actionType='toggle'
+ theme={theme}
+ />
+
+
+ {'@channel, @all, @here'}
+
+ )}
+ description={(
+
+ )}
+ action={this.toggleChannelMentions}
+ actionType='toggle'
+ selected={this.state.channel === 'true'}
+ theme={theme}
+ />
+
+
+ )}
+ description={mentionKeysComponent}
+ action={this.goToNotificationSettingsMentionKeywords}
+ actionType='arrow'
+ theme={theme}
+ />
+
+ );
+ }
+
+ renderReplySection(style) {
+ const {theme} = this.props;
+
+ return (
+
+
+ )}
+ action={this.setReplyNotifications}
+ actionType='select'
+ actionValue='any'
+ selected={this.state.comments === 'any'}
+ theme={theme}
+ />
+
+
+ )}
+ action={this.setReplyNotifications}
+ actionType='select'
+ actionValue='root'
+ selected={this.state.comments === 'root'}
+ theme={theme}
+ />
+
+
+ )}
+ action={this.setReplyNotifications}
+ actionType='select'
+ actionValue='never'
+ selected={this.state.comments === 'never'}
+ theme={theme}
+ />
+
+ );
+ }
+
+ render() {
+ const {theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return (
+
+
+
+ {this.renderMentionSection(style)}
+ {this.renderReplySection(style)}
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ },
+ input: {
+ color: theme.centerChannelColor,
+ fontSize: 12,
+ height: 40
+ },
+ separator: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ flex: 1,
+ height: 1,
+ marginLeft: 15
+ },
+ scrollView: {
+ flex: 1,
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06)
+ },
+ scrollViewContent: {
+ paddingVertical: 35
+ }
+ };
+});
+
+export default injectIntl(NotificationSettingsMentionsIos);
diff --git a/app/screens/settings/notification_settings_mentions_keywords/index.js b/app/screens/settings/notification_settings_mentions_keywords/index.js
new file mode 100644
index 000000000..7fe120834
--- /dev/null
+++ b/app/screens/settings/notification_settings_mentions_keywords/index.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getTheme} from 'app/selectors/preferences';
+
+import NotificationSettingsMentionsKeywords from './notification_settings_mentions_keywords';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps,
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(NotificationSettingsMentionsKeywords);
diff --git a/app/screens/settings/notification_settings_mentions_keywords/notification_settings_mentions_keywords.js b/app/screens/settings/notification_settings_mentions_keywords/notification_settings_mentions_keywords.js
new file mode 100644
index 000000000..4833ba160
--- /dev/null
+++ b/app/screens/settings/notification_settings_mentions_keywords/notification_settings_mentions_keywords.js
@@ -0,0 +1,130 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {View} from 'react-native';
+
+import FormattedText from 'app/components/formatted_text';
+import StatusBar from 'app/components/status_bar';
+import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default class NotificationSettingsMentionsKeywords extends PureComponent {
+ static propTypes = {
+ keywords: PropTypes.string,
+ navigator: PropTypes.object,
+ onBack: PropTypes.func.isRequired,
+ theme: PropTypes.object.isRequired
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ keywords: props.keywords
+ };
+
+ props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
+ }
+
+ handleSubmit = () => {
+ this.props.navigator.pop();
+ };
+
+ keywordsRef = (ref) => {
+ this.keywordsInput = ref;
+ };
+
+ onKeywordsChangeText = (keywords) => {
+ if (keywords.endsWith('\n')) {
+ return this.handleSubmit();
+ }
+
+ return this.setState({keywords});
+ };
+
+ onNavigatorEvent = (event) => {
+ if (event.type === 'ScreenChangedEvent') {
+ switch (event.id) {
+ case 'willDisappear':
+ this.props.onBack(this.state.keywords);
+ break;
+ }
+ }
+ };
+
+ render() {
+ const {theme} = this.props;
+ const {keywords} = this.state;
+
+ const style = getStyleSheet(theme);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ },
+ wrapper: {
+ flex: 1,
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
+ paddingTop: 35
+ },
+ inputContainer: {
+ borderTopWidth: 1,
+ borderBottomWidth: 1,
+ borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
+ borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
+ backgroundColor: theme.centerChannelBg
+ },
+ input: {
+ color: theme.centerChannelColor,
+ fontSize: 15,
+ height: 150,
+ paddingHorizontal: 15,
+ paddingVertical: 10
+ },
+ helpContainer: {
+ marginTop: 10,
+ paddingHorizontal: 15
+ },
+ help: {
+ color: changeOpacity(theme.centerChannelColor, 0.4),
+ fontSize: 13
+ }
+ };
+});
diff --git a/app/screens/settings/notification_settings_mobile/index.js b/app/screens/settings/notification_settings_mobile/index.js
new file mode 100644
index 000000000..9b57e5c38
--- /dev/null
+++ b/app/screens/settings/notification_settings_mobile/index.js
@@ -0,0 +1,20 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getConfig} from 'mattermost-redux/selectors/entities/general';
+
+import {getTheme} from 'app/selectors/preferences';
+
+import NotificationSettingsMobile from './notification_settings_mobile';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps,
+ config: getConfig(state),
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(NotificationSettingsMobile);
diff --git a/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js
new file mode 100644
index 000000000..3bcf6e4bb
--- /dev/null
+++ b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js
@@ -0,0 +1,437 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import {injectIntl} from 'react-intl';
+import {
+ Modal,
+ ScrollView,
+ TouchableOpacity,
+ View
+} from 'react-native';
+
+import FormattedText from 'app/components/formatted_text';
+import {RadioButton, RadioButtonGroup} from 'app/components/radio_button';
+import StatusBar from 'app/components/status_bar';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+import SectionItem from 'app/screens/settings/section_item';
+
+import NotificationSettingsMobileBase from './notification_settings_mobile_base';
+
+class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase {
+ cancelMobilePushModal = () => {
+ this.setState({showMobilePushModal: false});
+ this.push = this.state.push;
+ };
+
+ cancelMobilePushStatusModal = () => {
+ this.setState({showMobilePushStatusModal: false});
+ this.pushStatus = this.state.push_status;
+ };
+
+ onMobilePushChanged = (value) => {
+ this.push = value;
+ };
+
+ onMobilePushStatusChanged = (value) => {
+ this.pushStatus = value;
+ };
+
+ renderMobilePushModal(style) {
+ const {config, intl} = this.props;
+ const pushNotificationsEnabled = config.SendPushNotifications === 'true';
+
+ return (
+
+
+
+
+
+
+
+ {pushNotificationsEnabled &&
+
+
+
+
+
+ }
+ {!pushNotificationsEnabled &&
+
+ }
+
+
+
+
+
+
+
+ {pushNotificationsEnabled &&
+
+
+
+
+
+
+ }
+
+
+
+
+
+ );
+ }
+
+ renderMobilePushStatusModal(style) {
+ const {intl} = this.props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ renderMobilePushSection() {
+ const {config, theme} = this.props;
+
+ const pushNotificationsEnabled = config.SendPushNotifications === 'true';
+
+ let i18nId;
+ let i18nMessage;
+ const props = {};
+ if (pushNotificationsEnabled) {
+ switch (this.state.push) {
+ case 'all':
+ i18nId = 'user.settings.notifications.allActivity';
+ i18nMessage = 'For all activity';
+ break;
+ case 'mention':
+ i18nId = 'user.settings.notifications.onlyMentions';
+ i18nMessage = 'Only for mentions and direct messages';
+ break;
+ case 'none':
+ i18nId = 'user.settings.notifications.never';
+ i18nMessage = 'Never';
+ break;
+ }
+ props.description = (
+
+ );
+ } else {
+ props.description = (
+
+ );
+ }
+
+ return (
+
+ )}
+ action={this.showMobilePushModal}
+ actionType='default'
+ theme={theme}
+ />
+ );
+ }
+
+ renderMobilePushStatusSection(style) {
+ const {config, theme} = this.props;
+
+ const showSection = config.SendPushNotifications === 'true' && this.state.push !== 'none';
+ if (!showSection) {
+ return null;
+ }
+
+ let i18nId;
+ let i18nMessage;
+ switch (this.state.push_status) {
+ case 'online':
+ i18nId = 'user.settings.push_notification.online';
+ i18nMessage = 'Online, away or offline';
+ break;
+ case 'away':
+ i18nId = 'user.settings.push_notification.away';
+ i18nMessage = 'Away or offline';
+ break;
+ case 'offline':
+ i18nId = 'user.settings.push_notification.offline';
+ i18nMessage = 'Offline';
+ break;
+ }
+
+ return (
+
+
+ )}
+ label={(
+
+ )}
+ action={this.showMobilePushStatusModal}
+ actionType='default'
+ theme={theme}
+ />
+
+
+ );
+ }
+
+ saveMobilePushModal = () => {
+ this.setState({showMobilePushModal: false});
+ this.setMobilePush(this.push);
+ };
+
+ saveMobilePushStatusModal = () => {
+ this.setState({showMobilePushStatusModal: false});
+ this.setMobilePushStatus(this.pushStatus);
+ };
+
+ showMobilePushModal = () => {
+ this.setState({showMobilePushModal: true});
+ };
+
+ showMobilePushStatusModal = () => {
+ this.setState({showMobilePushStatusModal: true});
+ };
+
+ render() {
+ const {theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return (
+
+
+
+ {this.renderMobilePushSection()}
+
+ {this.renderMobilePushStatusSection(style)}
+
+ {this.renderMobilePushModal(style)}
+ {this.renderMobilePushStatusModal(style)}
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ },
+ input: {
+ color: theme.centerChannelColor,
+ fontSize: 12,
+ height: 40
+ },
+ separator: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ flex: 1,
+ height: 1
+ },
+ scrollView: {
+ flex: 1,
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06)
+ },
+ scrollViewContent: {
+ paddingVertical: 0
+ },
+ modalOverlay: {
+ backgroundColor: changeOpacity('#000000', 0.6),
+ alignItems: 'center',
+ flex: 1
+ },
+ modal: {
+ backgroundColor: theme.centerChannelBg,
+ borderRadius: 4,
+ marginTop: 20,
+ width: '95%'
+ },
+ modalBody: {
+ paddingHorizontal: 24
+ },
+ modalTitleContainer: {
+ marginBottom: 30,
+ marginTop: 20
+ },
+ modalTitle: {
+ color: theme.centerChannelColor,
+ fontSize: 19
+ },
+ modalOptionDisabled: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ fontSize: 17
+ },
+ modalFooter: {
+ alignItems: 'flex-end',
+ height: 58,
+ marginTop: 40,
+ width: '100%'
+ },
+ modalFooterContainer: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ paddingRight: 24
+ },
+ modalFooterOptionContainer: {
+ alignItems: 'center',
+ height: 40,
+ justifyContent: 'center',
+ paddingHorizontal: 10,
+ paddingVertical: 5
+ },
+ modalFooterOption: {
+ color: theme.linkColor,
+ fontSize: 14
+ }
+ };
+});
+
+export default injectIntl(NotificationSettingsMobileAndroid);
diff --git a/app/screens/settings/notification_settings_mobile/notification_settings_mobile.ios.js b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.ios.js
new file mode 100644
index 000000000..b3fa71f28
--- /dev/null
+++ b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.ios.js
@@ -0,0 +1,198 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import {injectIntl} from 'react-intl';
+import {
+ ScrollView,
+ View
+} from 'react-native';
+
+import FormattedText from 'app/components/formatted_text';
+import StatusBar from 'app/components/status_bar';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+import Section from 'app/screens/settings/section';
+import SectionItem from 'app/screens/settings/section_item';
+
+import NotificationSettingsMobileBase from './notification_settings_mobile_base';
+
+class NotificationSettingsMobileIos extends NotificationSettingsMobileBase {
+ renderMobilePushSection(style) {
+ const {config, theme} = this.props;
+
+ const pushNotificationsEnabled = config.SendPushNotifications === 'true';
+ return (
+
+ {pushNotificationsEnabled &&
+
+
+ )}
+ action={this.setMobilePush}
+ actionType='select'
+ actionValue='all'
+ selected={this.state.push === 'all'}
+ theme={theme}
+ />
+
+
+ )}
+ action={this.setMobilePush}
+ actionType='select'
+ actionValue='mention'
+ selected={this.state.push === 'mention'}
+ theme={theme}
+ />
+
+
+ )}
+ action={this.setMobilePush}
+ actionType='select'
+ actionValue='none'
+ selected={this.state.push === 'none'}
+ theme={theme}
+ />
+
+ }
+ {!pushNotificationsEnabled &&
+
+ }
+
+ );
+ }
+
+ renderMobilePushStatusSection(style) {
+ const {config, theme} = this.props;
+
+ const showSection = config.SendPushNotifications === 'true' && this.state.push !== 'none';
+ if (!showSection) {
+ return null;
+ }
+
+ return (
+
+
+ )}
+ action={this.setMobilePushStatus}
+ actionType='select'
+ actionValue='online'
+ selected={this.state.push_status === 'online'}
+ theme={theme}
+ />
+
+
+ )}
+ action={this.setMobilePushStatus}
+ actionType='select'
+ actionValue='away'
+ selected={this.state.push_status === 'away'}
+ theme={theme}
+ />
+
+
+ )}
+ action={this.setMobilePushStatus}
+ actionType='select'
+ actionValue='offline'
+ selected={this.state.push_status === 'offline'}
+ theme={theme}
+ />
+
+ );
+ }
+
+ render() {
+ const {theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return (
+
+
+
+ {this.renderMobilePushSection(style)}
+ {this.renderMobilePushStatusSection(style)}
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ },
+ input: {
+ color: theme.centerChannelColor,
+ fontSize: 12,
+ height: 40
+ },
+ separator: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ flex: 1,
+ height: 1,
+ marginLeft: 15
+ },
+ scrollView: {
+ flex: 1,
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.06)
+ },
+ scrollViewContent: {
+ paddingVertical: 35
+ },
+ disabled: {
+ color: theme.centerChannelColor,
+ fontSize: 15,
+ paddingHorizontal: 15,
+ paddingVertical: 10
+ }
+ };
+});
+
+export default injectIntl(NotificationSettingsMobileIos);
diff --git a/app/screens/settings/notification_settings_mobile/notification_settings_mobile_base.js b/app/screens/settings/notification_settings_mobile/notification_settings_mobile_base.js
new file mode 100644
index 000000000..71cdf51b5
--- /dev/null
+++ b/app/screens/settings/notification_settings_mobile/notification_settings_mobile_base.js
@@ -0,0 +1,62 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {intlShape} from 'react-intl';
+
+export default class NotificationSettingsMobileBase extends PureComponent {
+ static propTypes = {
+ config: PropTypes.object.isRequired,
+ currentUser: PropTypes.object.isRequired,
+ intl: intlShape.isRequired,
+ navigator: PropTypes.object,
+ onBack: PropTypes.func.isRequired,
+ theme: PropTypes.object.isRequired
+ };
+
+ constructor(props) {
+ super(props);
+
+ const {currentUser} = props;
+ const notifyProps = currentUser.notify_props || {};
+
+ this.state = {
+ ...notifyProps,
+ showMobilePushModal: false,
+ showMobilePushStatusModal: false
+ };
+ this.push = this.state.push;
+ this.pushStatus = this.state.push_status;
+ props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
+ }
+
+ onNavigatorEvent = (event) => {
+ if (event.type === 'ScreenChangedEvent') {
+ switch (event.id) {
+ case 'willDisappear':
+ this.saveUserNotifyProps();
+ break;
+ }
+ }
+ };
+
+ setMobilePush = (push) => {
+ this.setState({push});
+ };
+
+ setMobilePushStatus = (value) => {
+ this.setState({push_status: value});
+ };
+
+ saveUserNotifyProps = () => {
+ const props = {...this.state};
+
+ Reflect.deleteProperty(props, 'showMobilePushModal');
+ Reflect.deleteProperty(props, 'showMobilePushStatusModal');
+ this.props.onBack({
+ ...props,
+ user_id: this.props.currentUser.id
+ });
+ };
+}
diff --git a/app/screens/account_notifications/section.js b/app/screens/settings/section.js
similarity index 96%
rename from app/screens/account_notifications/section.js
rename to app/screens/settings/section.js
index 05308b728..dcb5d36f0 100644
--- a/app/screens/account_notifications/section.js
+++ b/app/screens/settings/section.js
@@ -10,33 +10,6 @@ import {
import FormattedText from 'app/components/formatted_text';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
-const getStyleSheet = makeStyleSheetFromTheme((theme) => {
- return {
- container: {
- marginTop: 30
- },
- footer: {
- marginHorizontal: 15,
- marginTop: 10,
- fontSize: 12,
- color: changeOpacity(theme.centerChannelColor, 0.5)
- },
- header: {
- marginHorizontal: 15,
- marginBottom: 10,
- fontSize: 13,
- color: theme.centerChannelColor
- },
- items: {
- backgroundColor: theme.centerChannelBg,
- borderTopWidth: 1,
- borderBottomWidth: 1,
- borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
- borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1)
- }
- };
-});
-
function section(props) {
const {
children,
@@ -87,4 +60,31 @@ section.propTypes = {
theme: PropTypes.object.isRequired
};
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ marginBottom: 30
+ },
+ header: {
+ marginHorizontal: 15,
+ marginBottom: 10,
+ fontSize: 13,
+ color: changeOpacity(theme.centerChannelColor, 0.5)
+ },
+ items: {
+ backgroundColor: theme.centerChannelBg,
+ borderTopWidth: 1,
+ borderBottomWidth: 1,
+ borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
+ borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1)
+ },
+ footer: {
+ marginHorizontal: 15,
+ marginTop: 10,
+ fontSize: 12,
+ color: changeOpacity(theme.centerChannelColor, 0.5)
+ }
+ };
+});
+
export default section;
diff --git a/app/screens/account_notifications/section_item.js b/app/screens/settings/section_item.js
similarity index 51%
rename from app/screens/account_notifications/section_item.js
rename to app/screens/settings/section_item.js
index 0159f4cf0..960564c13 100644
--- a/app/screens/account_notifications/section_item.js
+++ b/app/screens/settings/section_item.js
@@ -8,46 +8,27 @@ import {
TouchableWithoutFeedback,
View
} from 'react-native';
+import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
-import FormattedText from 'app/components/formatted_text';
-import {makeStyleSheetFromTheme} from 'app/utils/theme';
import CheckMark from 'app/components/checkmark';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
const ActionTypes = {
+ ARROW: 'arrow',
DEFAULT: 'default',
TOGGLE: 'toggle',
SELECT: 'select'
};
-const getStyleSheet = makeStyleSheetFromTheme((theme) => {
- return {
- container: {
- flexDirection: 'row',
- alignItems: 'center'
- },
- label: {
- flex: 1,
- fontSize: 12,
- color: theme.centerChannelColor,
- paddingVertical: 15
- },
- wrapper: {
- paddingHorizontal: 15
- }
- };
-});
-
function sectionItem(props) {
const {
action,
actionType,
actionValue,
- children,
- labelDefaultMessage,
- labelId,
- labelValues,
+ label,
theme,
- selected
+ selected,
+ description
} = props;
const style = getStyleSheet(theme);
@@ -68,24 +49,39 @@ function sectionItem(props) {
value={selected}
/>
);
+ } else if (actionType === ActionTypes.ARROW) {
+ actionComponent = (
+
+ );
+ }
+
+ const labelComponent = React.cloneElement(
+ label,
+ {style: style.label}
+ );
+
+ let descriptionComponent;
+ if (description) {
+ descriptionComponent = React.cloneElement(
+ description,
+ {style: style.description}
+ );
}
const component = (
-
-
-
- {actionComponent}
+
+
+ {labelComponent}
+ {descriptionComponent}
- {children}
+ {actionComponent}
);
- if (actionType === ActionTypes.DEFAULT || actionType === ActionTypes.SELECT) {
+ if (actionType === ActionTypes.DEFAULT || actionType === ActionTypes.SELECT || actionType === ActionTypes.ARROW) {
return (
action(actionValue)}>
{component}
@@ -98,14 +94,12 @@ function sectionItem(props) {
sectionItem.propTypes = {
action: PropTypes.func,
- actionType: PropTypes.oneOf([ActionTypes.DEFAULT, ActionTypes.TOGGLE, ActionTypes.SELECT]),
+ actionType: PropTypes.oneOf([ActionTypes.ARROW, ActionTypes.DEFAULT, ActionTypes.TOGGLE, ActionTypes.SELECT]),
actionValue: PropTypes.string,
- children: PropTypes.node,
- labelDefaultMessage: PropTypes.string.isRequired,
- labelId: PropTypes.string.isRequired,
- labelValues: PropTypes.object,
+ label: PropTypes.node.isRequired,
selected: PropTypes.bool,
- theme: PropTypes.object.isRequired
+ theme: PropTypes.object.isRequired,
+ description: PropTypes.node
};
sectionItem.defaultProps = {
@@ -113,4 +107,39 @@ sectionItem.defaultProps = {
actionType: ActionTypes.DEFAULT
};
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 15
+ },
+ singleContainer: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ height: 45
+ },
+ doubleContainer: {
+ flex: 1,
+ flexDirection: 'column',
+ height: 69,
+ justifyContent: 'center'
+ },
+ label: {
+ color: theme.centerChannelColor,
+ fontSize: 15
+ },
+ description: {
+ color: changeOpacity(theme.centerChannelColor, 0.6),
+ fontSize: 14,
+ marginTop: 3
+ },
+ arrow: {
+ color: changeOpacity(theme.centerChannelColor, 0.25),
+ fontSize: 24
+ }
+ };
+});
+
export default sectionItem;
diff --git a/app/screens/settings/settings_item.js b/app/screens/settings/settings_item.js
deleted file mode 100644
index 7484688cc..000000000
--- a/app/screens/settings/settings_item.js
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
-import {TouchableOpacity, View} from 'react-native';
-import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
-import IonIcon from 'react-native-vector-icons/Ionicons';
-import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
-
-import FormattedText from 'app/components/formatted_text';
-import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
-
-export default class SettingsItem extends PureComponent {
- static propTypes = {
- defaultMessage: PropTypes.string.isRequired,
- i18nId: PropTypes.string.isRequired,
- iconName: PropTypes.string.isRequired,
- iconType: PropTypes.oneOf(['fontawesome', 'ion', 'material']).isRequired,
- isDestructor: PropTypes.bool,
- onPress: PropTypes.func,
- separator: PropTypes.bool,
- theme: PropTypes.object.isRequired
- };
-
- static defaultProps = {
- isDestructor: false,
- separator: true
- };
-
- render() {
- const {
- defaultMessage,
- i18nId,
- iconName,
- iconType,
- isDestructor,
- onPress,
- separator,
- theme
- } = this.props;
- const style = getStyleSheet(theme);
-
- const destructor = {};
- if (isDestructor) {
- destructor.color = theme.errorTextColor;
- }
-
- let divider;
- if (separator) {
- divider = ();
- }
-
- let icon;
- switch (iconType) {
- case 'fontawesome':
- icon = (
-
- );
- break;
- case 'ion':
- icon = (
-
- );
- break;
- case 'material':
- icon = (
-
- );
- break;
- }
-
- return (
-
-
-
-
- {icon}
-
-
-
-
- {divider}
-
- );
- }
-}
-
-const getStyleSheet = makeStyleSheetFromTheme((theme) => {
- return {
- container: {
- backgroundColor: theme.centerChannelBg,
- height: 51,
- flexDirection: 'column',
- justifyContent: 'center'
- },
- wrapper: {
- alignItems: 'center',
- backgroundColor: theme.centerChannelBg,
- flexDirection: 'row',
- height: 50,
- paddingLeft: 16
- },
- iconContainer: {
- width: 18,
- marginRight: 15,
- alignItems: 'center',
- justifyContent: 'center'
- },
- icon: {
- color: changeOpacity(theme.centerChannelColor, 0.5),
- fontSize: 20
- },
- label: {
- color: theme.centerChannelColor,
- fontSize: 16,
- lineHeight: 19,
- marginLeft: 8
- },
- divider: {
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
- height: 1,
- marginHorizontal: 16
- }
- };
-});
diff --git a/app/screens/settings/settings_item/index.js b/app/screens/settings/settings_item/index.js
new file mode 100644
index 000000000..a58d821b4
--- /dev/null
+++ b/app/screens/settings/settings_item/index.js
@@ -0,0 +1,101 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {TouchableOpacity, View} from 'react-native';
+import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
+
+import FormattedText from 'app/components/formatted_text';
+
+import SettingItemIcon from './setting_item_icon';
+import getStyleSheet from './style';
+
+export default class SettingsItem extends PureComponent {
+ static propTypes = {
+ defaultMessage: PropTypes.string.isRequired,
+ i18nId: PropTypes.string.isRequired,
+ iconName: PropTypes.string,
+ iconType: PropTypes.oneOf(['fontawesome', 'foundation', 'ion', 'material']),
+ isDestructor: PropTypes.bool,
+ centered: PropTypes.bool,
+ onPress: PropTypes.func,
+ separator: PropTypes.bool,
+ showArrow: PropTypes.bool,
+ theme: PropTypes.object.isRequired
+ };
+
+ static defaultProps = {
+ isDestructor: false,
+ separator: true
+ };
+
+ render() {
+ const {
+ centered,
+ defaultMessage,
+ i18nId,
+ iconName,
+ iconType,
+ isDestructor,
+ onPress,
+ separator,
+ showArrow,
+ theme
+ } = this.props;
+ const style = getStyleSheet(theme);
+
+ const destructor = {};
+ if (isDestructor) {
+ destructor.color = theme.errorTextColor;
+ }
+
+ let divider;
+ if (separator) {
+ divider = ();
+ }
+
+ let icon;
+ if (iconType && iconName) {
+ icon = (
+
+ );
+ }
+
+ return (
+
+
+ {icon &&
+
+ {icon}
+
+ }
+
+
+
+ {showArrow &&
+
+
+
+ }
+
+ {divider}
+
+
+
+ );
+ }
+}
diff --git a/app/screens/settings/settings_item/setting_item_icon.js b/app/screens/settings/settings_item/setting_item_icon.js
new file mode 100644
index 000000000..1492d2ea4
--- /dev/null
+++ b/app/screens/settings/settings_item/setting_item_icon.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import IonIcon from 'react-native-vector-icons/Ionicons';
+import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
+import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
+import FoundationIcon from 'react-native-vector-icons/Foundation';
+
+export default function settingsItemIcon(props) {
+ const {name, type, style} = props;
+
+ switch (type) {
+ case 'fontawesome':
+ return (
+
+ );
+ case 'foundation':
+ return (
+
+ );
+ case 'ion':
+ return (
+
+ );
+ case 'material':
+ return (
+
+ );
+ }
+
+ return null;
+}
+
+settingsItemIcon.propTypes = {
+ name: PropTypes.string,
+ type: PropTypes.string,
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
+};
diff --git a/app/screens/settings/settings_item/style.android.js b/app/screens/settings/settings_item/style.android.js
new file mode 100644
index 000000000..17a532681
--- /dev/null
+++ b/app/screens/settings/settings_item/style.android.js
@@ -0,0 +1,49 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ alignItems: 'center',
+ backgroundColor: theme.centerChannelBg,
+ flexDirection: 'row',
+ height: 68
+ },
+ iconContainer: {
+ width: 42,
+ height: 68,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginHorizontal: 15
+ },
+ icon: {
+ color: theme.buttonBg,
+ fontSize: 25
+ },
+ wrapper: {
+ flex: 1
+ },
+ centerLabel: {
+ textAlign: 'center',
+ textAlignVertical: 'center'
+ },
+ labelContainer: {
+ flex: 1,
+ flexDirection: 'row'
+ },
+ label: {
+ color: theme.centerChannelColor,
+ flex: 1,
+ fontSize: 17,
+ textAlignVertical: 'center',
+ includeFontPadding: false,
+ paddingRight: 15
+ },
+ divider: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ height: 1
+ }
+ };
+});
diff --git a/app/screens/settings/settings_item/style.ios.js b/app/screens/settings/settings_item/style.ios.js
new file mode 100644
index 000000000..6702342a7
--- /dev/null
+++ b/app/screens/settings/settings_item/style.ios.js
@@ -0,0 +1,58 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ alignItems: 'center',
+ backgroundColor: theme.centerChannelBg,
+ flexDirection: 'row',
+ height: 45
+ },
+ iconContainer: {
+ width: 29,
+ height: 29,
+ backgroundColor: theme.buttonBg,
+ borderRadius: 7,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginHorizontal: 15
+ },
+ icon: {
+ color: theme.buttonColor,
+ fontSize: 18,
+ marginTop: 2
+ },
+ wrapper: {
+ flex: 1
+ },
+ centerLabel: {
+ textAlign: 'center',
+ textAlignVertical: 'center'
+ },
+ labelContainer: {
+ flex: 1,
+ flexDirection: 'row'
+ },
+ label: {
+ color: theme.centerChannelColor,
+ flex: 1,
+ fontSize: 17,
+ lineHeight: 43
+ },
+ arrowContainer: {
+ justifyContent: 'center',
+ paddingRight: 15
+ },
+ arrow: {
+ color: changeOpacity(theme.centerChannelColor, 0.25),
+ fontSize: 18
+ },
+ divider: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ height: 1
+ }
+ };
+});
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index f6a2fd001..21380fe11 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -1802,7 +1802,7 @@
"mobile.account.notifications.email.footer": "When offline or away for more than five minutes",
"mobile.account_notifications.mentions_footer": "Your username (\"@{username}\") will always trigger mentions.",
"mobile.account_notifications.non-case_sensitive_words": "Other non-case sensitive words...",
- "mobile.account_notifications.reply.header": "Send reply notifications for",
+ "mobile.account_notifications.reply.header": "SEND REPLY NOTIFICATIONS FOR",
"mobile.account_notifications.threads_mentions": "Mentions in threads",
"mobile.account_notifications.threads_start": "Threads that I start",
"mobile.account_notifications.threads_start_participate": "Threads that I start or participate in",
@@ -1884,6 +1884,25 @@
"mobile.notice_platform_link": "platform",
"mobile.notice_text": "Mattermost is made possible by the open source software used in our {platform} and {mobile}.",
"mobile.notification.in": " in ",
+ "mobile.notification_settings.email": "Email",
+ "mobile.notification_settings.email_title": "Email Notifications",
+ "mobile.notification_settings.mobile": "Mobile",
+ "mobile.notification_settings.mobile_title": "Mobile Notifications",
+ "mobile.notification_settings.save_failed_description": "The notification settings failed to save due to a connection issue, please try again.",
+ "mobile.notification_settings.save_failed_title": "Connection issue",
+ "mobile.notification_settings.mentions.reply_title": "Send Reply notifications for",
+ "mobile.notification_settings.mentions_replies": "Mentions and Replies",
+ "mobile.notification_settings.mentions.channelWide": "Channel-wide mentions",
+ "mobile.notification_settings_mentions.keywords": "Keywords",
+ "mobile.notification_settings_mentions.keywordsDescription": "Other words that trigger a mention",
+ "mobile.notification_settings_mentions.keywordsHelp": "Keywords are non-case sensitive and should be separated by a comma.",
+ "mobile.notification_settings.mentions.sensitiveName": "Your case sensitive first name",
+ "mobile.notification_settings.mentions.sensitiveUsername": "Your non-case sensitive username",
+ "mobile.notification_settings_mentions.wordsTrigger": "WORDS THAT TRIGGER MENTIONS",
+ "mobile.notification_settings_mobile.push_activity": "SEND NOTIFICATIONS FOR",
+ "mobile.notification_settings_mobile.push_activity_android": "Send notifications for",
+ "mobile.notification_settings_model.push_status": "TRIGGER PUSH NOTIFICATIONS WHEN",
+ "mobile.notification_settings_model.push_status_android": "Trigger push notifications when",
"mobile.offlineIndicator.connected": "Connected",
"mobile.offlineIndicator.connecting": "Connecting...",
"mobile.offlineIndicator.offline": "No internet connection",