From 4d8fa314d1b1735f081aea31f0bb6dcd77d7659b Mon Sep 17 00:00:00 2001 From: Saturnino Abril Date: Tue, 17 Sep 2019 09:39:12 +0800 Subject: [PATCH] MM-14273 Add boolean element support to interactive dialog (#3239) * add boolean element support to interactive dialog * determine landscape orientation by Dimension --- .../widgets/settings/bool_setting.js | 184 ++++++++++++++++++ .../widgets/settings/bool_setting.test.js | 35 ++++ .../interactive_dialog/dialog_element.js | 20 +- .../interactive_dialog/dialog_element.test.js | 3 +- .../interactive_dialog/interactive_dialog.js | 23 ++- 5 files changed, 260 insertions(+), 5 deletions(-) create mode 100644 app/components/widgets/settings/bool_setting.js create mode 100644 app/components/widgets/settings/bool_setting.test.js diff --git a/app/components/widgets/settings/bool_setting.js b/app/components/widgets/settings/bool_setting.js new file mode 100644 index 000000000..c9e7f324c --- /dev/null +++ b/app/components/widgets/settings/bool_setting.js @@ -0,0 +1,184 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; +import { + View, + Text, + Switch, +} from 'react-native'; + +import FormattedText from 'app/components/formatted_text'; +import { + changeOpacity, + makeStyleSheetFromTheme, +} from 'app/utils/theme'; +import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; + +export default class BoolSetting extends PureComponent { + static propTypes = { + id: PropTypes.string.isRequired, + label: PropTypes.oneOfType([ + PropTypes.shape({ + id: PropTypes.string.isRequired, + defaultMessage: PropTypes.string.isRequired, + }), + PropTypes.string, + ]), + value: PropTypes.bool.isRequired, + placeholder: PropTypes.string, + helpText: PropTypes.node, + errorText: PropTypes.node, + optional: PropTypes.bool, + theme: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + isLandscape: PropTypes.bool.isRequired, + }; + + static defaultProps = { + isLandscape: false, + }; + + handleChange = (value) => { + this.props.onChange(this.props.id, `${value}`); + }; + + render() { + const { + label, + value, + placeholder, + helpText, + errorText, + optional, + theme, + isLandscape, + } = this.props; + const style = getStyleSheet(theme); + + let optionalContent; + let asterisk; + if (optional) { + optionalContent = ( + + ); + } else { + asterisk = {' *'}; + } + + let labelContent; + if (label) { + labelContent = ( + + + {label} + + {asterisk} + {optionalContent} + + + ); + } + + let helpTextContent; + if (helpText) { + helpTextContent = ( + + {helpText} + + ); + } + + let errorTextContent; + if (errorText) { + errorTextContent = ( + + {errorText} + + ); + } + + return ( + + {labelContent} + + + + {placeholder} + + + + + + {helpTextContent} + {errorTextContent} + + + ); + } +} + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + labelContainer: { + flexDirection: 'row', + marginTop: 15, + marginBottom: 10, + }, + label: { + fontSize: 14, + color: theme.centerChannelColor, + marginLeft: 15, + }, + inputContainer: { + backgroundColor: theme.centerChannelBg, + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 15, + height: 40, + }, + placeholderText: { + color: changeOpacity(theme.centerChannelColor, 0.5), + fontSize: 15, + }, + inputSwitch: { + position: 'absolute', + right: 12, + }, + separator: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), + height: 1, + width: '100%', + }, + optional: { + color: changeOpacity(theme.centerChannelColor, 0.5), + fontSize: 14, + marginLeft: 5, + }, + helpText: { + fontSize: 12, + color: changeOpacity(theme.centerChannelColor, 0.5), + marginHorizontal: 15, + marginTop: 10, + }, + errorText: { + fontSize: 12, + color: theme.errorTextColor, + marginHorizontal: 15, + marginVertical: 10, + }, + asterisk: { + color: theme.errorTextColor, + fontSize: 14, + }, + }; +}); diff --git a/app/components/widgets/settings/bool_setting.test.js b/app/components/widgets/settings/bool_setting.test.js new file mode 100644 index 000000000..72c9da7f7 --- /dev/null +++ b/app/components/widgets/settings/bool_setting.test.js @@ -0,0 +1,35 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import React from 'react'; +import {shallow} from 'enzyme'; + +import Preferences from 'mattermost-redux/constants/preferences'; + +import BoolSetting from './bool_setting.js'; + +describe('components/widgets/settings/TextSetting', () => { + const theme = Preferences.THEMES.default; + test('onChange', () => { + const onChange = jest.fn(); + const wrapper = shallow( + + ); + + wrapper.instance().handleChange(false); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith('elementid', 'false'); + + wrapper.instance().handleChange(true); + expect(onChange).toHaveBeenCalledTimes(2); + expect(onChange).toHaveBeenCalledWith('elementid', 'true'); + }); +}); diff --git a/app/screens/interactive_dialog/dialog_element.js b/app/screens/interactive_dialog/dialog_element.js index 59bcf61fe..89b5f734e 100644 --- a/app/screens/interactive_dialog/dialog_element.js +++ b/app/screens/interactive_dialog/dialog_element.js @@ -4,6 +4,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; +import BoolSetting from 'app/components/widgets/settings/bool_setting'; import TextSetting from 'app/components/widgets/settings/text_setting'; import AutocompleteSelector from 'app/components/autocomplete_selector'; @@ -26,6 +27,7 @@ export default class DialogElement extends PureComponent { value: PropTypes.any, onChange: PropTypes.func, theme: PropTypes.object, + isLandscape: PropTypes.bool.isRequired, }; constructor(props) { @@ -70,6 +72,7 @@ export default class DialogElement extends PureComponent { theme, dataSource, options, + isLandscape, } = this.props; let {maxLength} = this.props; @@ -133,8 +136,23 @@ export default class DialogElement extends PureComponent { roundedBorders={false} /> ); + } else if (type === 'bool') { + return ( + + ); } return null; } -} \ No newline at end of file +} diff --git a/app/screens/interactive_dialog/dialog_element.test.js b/app/screens/interactive_dialog/dialog_element.test.js index cf357fbe2..2b64e9d83 100644 --- a/app/screens/interactive_dialog/dialog_element.test.js +++ b/app/screens/interactive_dialog/dialog_element.test.js @@ -13,6 +13,7 @@ describe('DialogElement', () => { displayName: 'Testing', name: 'testing', type: 'text', + isLandscape: false, }; const theme = Preferences.THEMES.default; test('secureTextEntry is true and multiline is false when subtype is password', () => { @@ -36,4 +37,4 @@ describe('DialogElement', () => { ); expect(wrapper.find({secureTextEntry: false}).exists()).toBe(true); }); -}); \ No newline at end of file +}); diff --git a/app/screens/interactive_dialog/interactive_dialog.js b/app/screens/interactive_dialog/interactive_dialog.js index e6bd696bb..8ac992323 100644 --- a/app/screens/interactive_dialog/interactive_dialog.js +++ b/app/screens/interactive_dialog/interactive_dialog.js @@ -3,7 +3,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {ScrollView, View} from 'react-native'; +import {Dimensions, ScrollView, View} from 'react-native'; import {Navigation} from 'react-native-navigation'; import {checkDialogElementForError, checkIfErrorsMatchElements} from 'mattermost-redux/utils/integration_utils'; @@ -49,12 +49,27 @@ export default class InteractiveDialog extends PureComponent { this.state = { values, errors: {}, + isLandscape: this.isLandscape(), submitting: false, }; } componentDidMount() { this.navigationEventListener = Navigation.events().bindComponent(this); + Dimensions.addEventListener('change', this.orientationDidChange); + } + + componentWillUnmount() { + Dimensions.removeEventListener('change', this.orientationDidChange); + } + + orientationDidChange = () => { + this.setState({isLandscape: this.isLandscape()}); + } + + isLandscape = () => { + const {height, width} = Dimensions.get('window'); + return width > height; } navigationButtonPressed({buttonId}) { @@ -153,6 +168,7 @@ export default class InteractiveDialog extends PureComponent { render() { const {introductionText, elements, theme} = this.props; + const {errors, isLandscape, values} = this.state; const style = getStyleFromTheme(theme); return ( @@ -174,16 +190,17 @@ export default class InteractiveDialog extends PureComponent { type={e.type} subtype={e.subtype} helpText={e.help_text} - errorText={this.state.errors[e.name]} + errorText={errors[e.name]} placeholder={e.placeholder} minLength={e.min_length} maxLength={e.max_length} dataSource={e.data_source} optional={e.optional} options={e.options} - value={this.state.values[e.name]} + value={values[e.name]} onChange={this.onChange} theme={theme} + isLandscape={isLandscape} /> ); })}