Clock Display Settings (#1334)

* Clock Display Settings

Add api call for saving militaryTime

WIP Android Clock Display Modal

Clean up styles for iOS and Android

header info no longer required in Sectin component

Add Display Settings option

Clean up Advance Settings

* Add requested changes

* Move Display options below Notifications option
This commit is contained in:
Chris Duarte 2018-01-19 14:37:03 -08:00 committed by enahum
parent 6ba81644c8
commit 8b62ec7133
9 changed files with 547 additions and 8 deletions

View file

@ -0,0 +1,211 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
TouchableOpacity,
Modal,
View
} from 'react-native';
import StatusBar from 'app/components/status_bar';
import FormattedText from 'app/components/formatted_text';
import RadioButtonGroup from 'app/components/radio_button';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ClockDisplayBase from './clock_display_base';
export default class ClockDisplay extends ClockDisplayBase {
static propTypes = {
showModal: PropTypes.bool.isRequired,
militaryTime: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired
};
setMilitaryTime = (value) => {
this.setState({
newMilitaryTime: value
});
};
closeModal = () => {
const {militaryTime, onClose} = this.props;
this.setState({
newMilitaryTime: militaryTime
});
onClose();
};
saveSelection = () => {
const {newMilitaryTime} = this.state;
const {onClose} = this.props;
this.saveClockDisplayPreference(newMilitaryTime);
onClose();
};
renderClockDisplayModal = (style) => {
const {showModal} = this.props;
const {intl} = this.context;
const {newMilitaryTime} = this.state;
const options = [{
label: intl.formatMessage({
id: 'user.settings.display.normalClock',
defaultMessage: '12-hour clock (example: 4:00 PM)'
}),
value: 'false',
checked: newMilitaryTime === 'false'
}, {
label: intl.formatMessage({
id: 'user.settings.display.militaryClock',
defaultMessage: '24-hour clock (example: 16:00)'
}),
value: 'true',
checked: newMilitaryTime === 'true'
}];
return (
<Modal
animationType='slide'
transparent={true}
visible={showModal}
onRequestClose={this.closeModal}
>
<View style={style.modalOverlay}>
<View style={style.modal}>
<View style={style.modalBody}>
<View style={style.modalTitleContainer}>
<FormattedText
id='mobile.advanced_settings.clockDisplay'
defaultMessage='Clock display'
style={style.modalTitle}
/>
</View>
<RadioButtonGroup
name='replySettings'
onSelect={this.setMilitaryTime}
options={options}
/>
<FormattedText
id='user.settings.display.preferTime'
defaultMessage='Select how you prefer time displayed.'
style={style.modalHelpText}
/>
</View>
<View style={style.modalFooter}>
<View style={style.separator}/>
<View style={style.modalFooterContainer}>
<TouchableOpacity
style={style.modalFooterOptionContainer}
onPress={this.closeModal}
>
<FormattedText
id='mobile.notification_settings.modal_cancel'
defaultMessage='CANCEL'
style={style.modalFooterOption}
/>
</TouchableOpacity>
<View style={style.modalFooterButtonSpacer}/>
<TouchableOpacity
style={style.modalFooterOptionContainer}
onPress={this.saveSelection}
>
<FormattedText
id='mobile.notification_settings.modal_save'
defaultMessage='SAVE'
style={style.modalFooterOption}
/>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</Modal>
);
};
render() {
const {theme} = this.props;
const style = getStyleSheet(theme);
return (
<View style={style.container}>
<StatusBar/>
<View style={style.wrapper}>
{this.renderClockDisplayModal(style)}
</View>
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
backgroundColor: theme.centerChannelBg
},
wrapper: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
flex: 1
},
separator: {
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
},
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
},
modalFooterButtonSpacer: {
marginRight: 10
}
};
});

View file

@ -0,0 +1,77 @@
import React from 'react';
import {
View
} from 'react-native';
import StatusBar from 'app/components/status_bar';
import Section from 'app/screens/settings/section';
import SectionItem from 'app/screens/settings/section_item';
import FormattedText from 'app/components/formatted_text';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ClockDisplayBase from './clock_display_base';
export default class ClockDisplay extends ClockDisplayBase {
render() {
const {theme} = this.props;
const {newMilitaryTime} = this.state;
const style = getStyleSheet(theme);
return (
<View style={style.container}>
<StatusBar/>
<View style={style.wrapper}>
<Section
disableHeader={true}
footerId='user.settings.display.preferTime'
footerDefaultMessage='Select how you prefer time displayed.'
theme={theme}
>
<SectionItem
label={(
<FormattedText
id='user.settings.display.normalClock'
defaultMessage='12-hour clock (example: 4:00 PM)'
/>
)}
action={this.setMilitaryTime}
actionType='select'
actionValue='false'
selected={newMilitaryTime === 'false'}
theme={theme}
/>
<View style={style.separator}/>
<SectionItem
label={(
<FormattedText
id='user.settings.display.militaryClock'
defaultMessage='24-hour clock (example: 16:00)'
/>
)}
action={this.setMilitaryTime}
actionType='select'
actionValue='true'
selected={newMilitaryTime === 'true'}
theme={theme}
/>
</Section>
</View>
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
backgroundColor: theme.centerChannelBg
},
wrapper: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
flex: 1,
paddingTop: 35
}
};
});

View file

@ -0,0 +1,51 @@
// 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';
import Preferences from 'mattermost-redux/constants/preferences';
export default class ClockDisplayBase extends PureComponent {
static propTypes = {
theme: PropTypes.object.isRequired,
militaryTime: PropTypes.string.isRequired,
userId: PropTypes.string.isRequired,
actions: PropTypes.shape({
savePreferences: PropTypes.func.isRequired
}).isRequired
};
static contextTypes = {
intl: intlShape.isRequired
};
constructor(props) {
super(props);
this.state = {
newMilitaryTime: props.militaryTime
};
}
setMilitaryTime = (value) => {
this.setState({
newMilitaryTime: value
});
this.saveClockDisplayPreference(value);
};
saveClockDisplayPreference = (newMilitaryTime) => {
const {userId, actions: {savePreferences}} = this.props;
const timePreference = {
user_id: userId,
category: Preferences.CATEGORY_DISPLAY_SETTINGS,
name: 'use_military_time',
value: newMilitaryTime
};
savePreferences(userId, [timePreference]);
}
}

View file

@ -0,0 +1,33 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getTheme, get as getPreference} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {savePreferences} from 'mattermost-redux/actions/preferences';
import Preferences from 'mattermost-redux/constants/preferences';
import ClockDisplay from './clock_display';
function mapStateToProps(state) {
const militaryTime = getPreference(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time') || 'false';
const currentUserId = getCurrentUserId(state);
return {
userId: currentUserId,
theme: getTheme(state),
militaryTime
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
savePreferences
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ClockDisplay);

View file

@ -14,8 +14,10 @@ import ChannelInfo from 'app/screens/channel_info';
import ChannelMembers from 'app/screens/channel_members';
import ChannelPeek from 'app/screens/channel_peek';
import ClientUpgrade from 'app/screens/client_upgrade';
import ClockDisplay from 'app/screens/clock_display';
import Code from 'app/screens/code';
import CreateChannel from 'app/screens/create_channel';
import DisplaySettings from 'app/screens/settings/display_settings';
import EditChannel from 'app/screens/edit_channel';
import EditPost from 'app/screens/edit_post';
import EditProfile from 'app/screens/edit_profile';
@ -68,8 +70,10 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(ChannelMembers), store, Provider);
Navigation.registerComponent('ChannelPeek', () => wrapWithContextProvider(ChannelPeek), store, Provider);
Navigation.registerComponent('ClientUpgrade', () => wrapWithContextProvider(ClientUpgrade), store, Provider);
Navigation.registerComponent('ClockDisplay', () => wrapWithContextProvider(ClockDisplay), store, Provider);
Navigation.registerComponent('Code', () => wrapWithContextProvider(Code), store, Provider);
Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(CreateChannel), store, Provider);
Navigation.registerComponent('DisplaySettings', () => wrapWithContextProvider(DisplaySettings), store, Provider);
Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(EditChannel), store, Provider);
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(EditPost), store, Provider);
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(EditProfile), store, Provider);

View file

@ -0,0 +1,118 @@
// 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 {intlShape} from 'react-intl';
import {
Platform,
View
} from 'react-native';
import SettingsItem from 'app/screens/settings/settings_item';
import StatusBar from 'app/components/status_bar';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ClockDisplay from 'app/screens/clock_display';
export default class DisplaySettings extends PureComponent {
static propTypes = {
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
static contextTypes = {
intl: intlShape.isRequired
};
state = {
showClockDisplaySettings: false
};
goToClockDisplaySettings = wrapWithPreventDoubleTap(() => {
const {navigator, theme} = this.props;
const {intl} = this.context;
if (Platform.OS === 'ios') {
navigator.push({
screen: 'ClockDisplay',
title: intl.formatMessage({id: 'user.settings.display.clockDisplay', defaultMessage: 'Clock Display'}),
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg
}
});
return;
}
this.setState({showClockDisplaySettings: true});
});
closeClockDisplaySettings = () => {
this.setState({showClockDisplaySettings: false});
};
render() {
const {theme} = this.props;
const {showClockDisplaySettings} = this.state;
const style = getStyleSheet(theme);
let clockDisplayModal;
if (Platform.OS === 'android') {
clockDisplayModal = (
<ClockDisplay
showModal={showClockDisplaySettings}
onClose={this.closeClockDisplaySettings}
/>
);
}
return (
<View style={style.container}>
<StatusBar/>
<View style={style.wrapper}>
<View style={style.divider}/>
<SettingsItem
defaultMessage='Clock Display'
i18nId='mobile.advanced_settings.clockDisplay'
iconName='ios-time'
iconType='ion'
onPress={this.goToClockDisplaySettings}
separator={false}
showArrow={false}
theme={theme}
/>
<View style={style.divider}/>
</View>
{clockDisplayModal}
</View>
);
}
}
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
}
};
});

View file

@ -0,0 +1,16 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import DisplaySettings from './display_settings';
function mapStateToProps(state) {
return {
theme: getTheme(state)
};
}
export default connect(mapStateToProps)(DisplaySettings);

View file

@ -105,6 +105,22 @@ class Settings extends PureComponent {
});
});
goToDisplaySettings = wrapWithPreventDoubleTap(() => {
const {intl, navigator, theme} = this.props;
navigator.push({
screen: 'DisplaySettings',
title: intl.formatMessage({id: 'user.settings.modal.display', defaultMessage: 'Display'}),
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg
}
});
});
goToAdvancedSettings = wrapWithPreventDoubleTap(() => {
const {intl, navigator, theme} = this.props;
navigator.push({
@ -221,6 +237,15 @@ class Settings extends PureComponent {
showArrow={showArrow}
theme={theme}
/>
<SettingsItem
defaultMessage='Display'
i18nId='user.settings.modal.display'
iconName='ios-apps'
iconType='ion'
onPress={this.goToDisplaySettings}
showArrow={showArrow}
theme={theme}
/>
{showTeams &&
<SettingsItem
defaultMessage='Open teams you can join'

View file

@ -13,6 +13,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
function section(props) {
const {
children,
disableHeader,
disableFooter,
footerDefaultMessage,
footerId,
@ -27,12 +28,14 @@ function section(props) {
return (
<View style={style.container}>
<FormattedText
id={headerId}
defaultMessage={headerDefaultMessage}
values={headerValues}
style={style.header}
/>
{(headerId && !disableHeader) &&
<FormattedText
id={headerId}
defaultMessage={headerDefaultMessage}
values={headerValues}
style={style.header}
/>
}
<View style={style.items}>
{children}
</View>
@ -50,12 +53,13 @@ function section(props) {
section.propTypes = {
children: PropTypes.node.isRequired,
disableHeader: PropTypes.bool,
disableFooter: PropTypes.bool,
footerDefaultMessage: PropTypes.string,
footerId: PropTypes.string,
footerValues: PropTypes.object,
headerDefaultMessage: PropTypes.string.isRequired,
headerId: PropTypes.string.isRequired,
headerDefaultMessage: PropTypes.string,
headerId: PropTypes.string,
headerValues: PropTypes.object,
theme: PropTypes.object.isRequired
};