Added KeyboardLayout to automatically use a KeyboardAvoidingView as necessary (#263)

* Renamed layouts/root_layout to components/root

* Added KeyboardLayout to automatically use a KeyboardAvoidingView when necessary
This commit is contained in:
Harrison Healey 2017-02-16 09:19:48 -05:00 committed by GitHub
parent 185b968b76
commit 1319f1fcf8
10 changed files with 214 additions and 268 deletions

View file

@ -0,0 +1,35 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {KeyboardAvoidingView, Platform, View} from 'react-native';
export default class KeyboardLayout extends React.Component {
static propTypes = {
behaviour: React.PropTypes.string,
children: React.PropTypes.node,
keyboardVerticalOffset: React.PropTypes.number
};
render() {
const {behaviour, children, keyboardVerticalOffset, ...otherProps} = this.props;
if (Platform.OS === 'android') {
return (
<View {...otherProps}>
{children}
</View>
);
}
return (
<KeyboardAvoidingView
behaviour={behaviour}
keyboardVerticalOffset={keyboardVerticalOffset}
{...otherProps}
>
{children}
</KeyboardAvoidingView>
);
}
}

View file

@ -0,0 +1,6 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import RootContainer from './root_container';
export default RootContainer;

View file

@ -6,7 +6,7 @@ import {AppState} from 'react-native';
import {getTranslations} from 'service/i18n';
import {IntlProvider} from 'react-intl';
export default class RootLayout extends React.Component {
export default class Root extends React.Component {
static propTypes = {
children: React.PropTypes.node,
locale: React.PropTypes.string.isRequired,

View file

@ -8,7 +8,7 @@ import Config from 'assets/config.json';
import {setAppState} from 'service/actions/general';
import RootLayout from './root_layout';
import Root from './root';
function mapStateToProps(state, ownProps) {
const users = state.entities.users;
@ -33,4 +33,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(RootLayout);
export default connect(mapStateToProps, mapDispatchToProps)(Root);

View file

@ -5,10 +5,10 @@ import 'harmony-reflect';
import React from 'react';
import {Provider} from 'react-redux';
import configureStore from 'app/store';
import Root from 'app/components/root';
import initialState from 'app/initial_state';
import Router from 'app/navigation/router';
import RootLayout from 'app/layouts/root_layout/root_layout_container';
import configureStore from 'app/store';
const store = configureStore(initialState);
@ -16,9 +16,9 @@ export default class Mattermost extends React.Component {
render() {
return (
<Provider store={store}>
<RootLayout>
<Root>
<Router/>
</RootLayout>
</Root>
</Provider>
);
}

View file

@ -3,15 +3,14 @@
import React from 'react';
import {
KeyboardAvoidingView,
Platform,
StatusBar,
Text,
View
Text
} from 'react-native';
import {Constants} from 'service/constants';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import PostTextbox from 'app/components/post_textbox';
import {Constants} from 'service/constants';
import EventEmitter from 'service/utils/event_emitter';
import ChannelDrawerButton from './channel_drawer_button';
@ -106,15 +105,6 @@ export default class Channel extends React.PureComponent {
this.props.actions.selectFirstAvailableTeam();
};
renderAndroid = (children) => {
const {theme} = this.props;
return (
<View style={{flex: 1, backgroundColor: theme.centerChannelBg}}>
{children}
</View>
);
};
render() {
const {
currentTeam,
@ -133,43 +123,22 @@ export default class Channel extends React.PureComponent {
teamId = currentTeam.id;
}
const status = (
<StatusBar
key='statusBar'
barStyle='light-content'
/>
);
const postList = (
<ChannelPostList
key='postList'
channel={currentChannel}
/>
);
const postTextBox = (
<PostTextbox
ref={this.attachPostTextbox}
key='postTextBox'
value={this.props.drafts[this.props.currentChannel.id]}
teamId={teamId}
channelId={currentChannel.id}
onChangeText={this.handleDraftChanged}
/>
);
if (Platform.OS === 'android') {
return this.renderAndroid([status, postList, postTextBox]);
}
return (
<KeyboardAvoidingView
<KeyboardLayout
behavior='padding'
style={{flex: 1, backgroundColor: theme.centerChannelBg}}
keyboardVerticalOffset={65}
>
{status}
{postList}
{postTextBox}
</KeyboardAvoidingView>
<StatusBar barStyle='light-content'/>
<ChannelPostList channel={currentChannel}/>
<PostTextbox
ref={this.attachPostTextbox}
value={this.props.drafts[this.props.currentChannel.id]}
teamId={teamId}
channelId={currentChannel.id}
onChangeText={this.handleDraftChanged}
/>
</KeyboardLayout>
);
}
}

View file

@ -6,17 +6,16 @@ import {injectIntl, intlShape} from 'react-intl';
import {
Image,
Keyboard,
KeyboardAvoidingView,
Platform,
Text,
TextInput,
TouchableWithoutFeedback,
View
} from 'react-native';
import Button from 'react-native-button';
import FormattedText from 'app/components/formatted_text';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import Loading from 'app/components/loading';
import {GlobalStyles} from 'app/styles';
@ -176,73 +175,65 @@ class Login extends Component {
return <Loading/>;
}
const scene = (
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
<Image
source={logo}
/>
<Text style={GlobalStyles.header}>
{this.props.config.SiteName}
</Text>
<FormattedText
style={GlobalStyles.subheader}
id='web.root.signup_info'
defaultMessage='All team communication in one place, searchable and accessible anywhere'
/>
<ErrorText error={this.props.loginRequest.error || this.props.checkMfaRequest.error || this.state.error}/>
<TextInput
ref={this.loginRef}
value={this.props.loginId}
onChangeText={this.props.actions.handleLoginIdChanged}
style={GlobalStyles.inputBox}
placeholder={this.createLoginPlaceholder()}
autoCorrect={false}
autoCapitalize='none'
keyboardType='email-address'
returnKeyType='next'
underlineColorAndroid='transparent'
onSubmitEditing={this.passwordFocus}
/>
<TextInput
ref={this.passwordRef}
value={this.props.password}
onChangeText={this.props.actions.handlePasswordChanged}
style={GlobalStyles.inputBox}
placeholder={this.props.intl.formatMessage({id: 'login.password', defaultMessage: 'Password'})}
secureTextEntry={true}
autoCorrect={false}
autoCapitalize='none'
underlineColorAndroid='transparent'
returnKeyType='go'
onSubmitEditing={this.preSignIn}
/>
<Button
onPress={this.preSignIn}
containerStyle={GlobalStyles.signupButton}
>
<FormattedText
id='login.signIn'
defaultMessage='Sign in'
style={GlobalStyles.signupButtonText}
/>
</Button>
</View>
</TouchableWithoutFeedback>
);
if (Platform.OS === 'android') {
return scene;
}
return (
<KeyboardAvoidingView
<KeyboardLayout
behavior='padding'
style={{flex: 1}}
keyboardVerticalOffset={65}
>
{scene}
</KeyboardAvoidingView>
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
<Image
source={logo}
/>
<Text style={GlobalStyles.header}>
{this.props.config.SiteName}
</Text>
<FormattedText
style={GlobalStyles.subheader}
id='web.root.signup_info'
defaultMessage='All team communication in one place, searchable and accessible anywhere'
/>
<ErrorText error={this.props.loginRequest.error || this.props.checkMfaRequest.error || this.state.error}/>
<TextInput
ref={this.loginRef}
value={this.props.loginId}
onChangeText={this.props.actions.handleLoginIdChanged}
style={GlobalStyles.inputBox}
placeholder={this.createLoginPlaceholder()}
autoCorrect={false}
autoCapitalize='none'
keyboardType='email-address'
returnKeyType='next'
underlineColorAndroid='transparent'
onSubmitEditing={this.passwordFocus}
/>
<TextInput
ref={this.passwordRef}
value={this.props.password}
onChangeText={this.props.actions.handlePasswordChanged}
style={GlobalStyles.inputBox}
placeholder={this.props.intl.formatMessage({id: 'login.password', defaultMessage: 'Password'})}
secureTextEntry={true}
autoCorrect={false}
autoCapitalize='none'
underlineColorAndroid='transparent'
returnKeyType='go'
onSubmitEditing={this.preSignIn}
/>
<Button
onPress={this.preSignIn}
containerStyle={GlobalStyles.signupButton}
>
<FormattedText
id='login.signIn'
defaultMessage='Sign in'
style={GlobalStyles.signupButtonText}
/>
</Button>
</View>
</TouchableWithoutFeedback>
</KeyboardLayout>
);
}
}

View file

@ -5,15 +5,14 @@ import React, {Component} from 'react';
import {
Image,
Keyboard,
KeyboardAvoidingView,
Platform,
TouchableWithoutFeedback,
View
} from 'react-native';
import Button from 'react-native-button';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
import {GlobalStyles} from 'app/styles';
@ -80,58 +79,50 @@ export default class Mfa extends Component {
};
render() {
const scene = (
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
<Image
source={logo}
/>
<FormattedText
style={[GlobalStyles.header, GlobalStyles.label]}
id='login_mfa.enterToken'
defaultMessage="To complete the sign in process, please enter a token from your smartphone's authenticator"
/>
<ErrorText error={this.state.error}/>
<TextInputWithLocalizedPlaceholder
ref={this.inputRef}
value={this.state.token}
onChangeText={this.handleInput}
onSubmitEditing={this.submit}
style={GlobalStyles.inputBox}
autoCapitalize='none'
autoCorrect={false}
keyboardType='numeric'
placeholder={{id: 'login_mfa.token', defaultMessage: 'MFA Token'}}
returnKeyType='go'
underlineColorAndroid='transparent'
/>
<Button
onPress={this.submit}
loading={false}
containerStyle={GlobalStyles.signupButton}
>
<FormattedText
style={GlobalStyles.signupButtonText}
id='mobile.components.select_server_view.proceed'
defaultMessage='Proceed'
/>
</Button>
</View>
</TouchableWithoutFeedback>
);
if (Platform.OS === 'android') {
return scene;
}
return (
<KeyboardAvoidingView
<KeyboardLayout
behavior='padding'
style={{flex: 1}}
keyboardVerticalOffset={0}
>
{scene}
</KeyboardAvoidingView>
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
<Image
source={logo}
/>
<FormattedText
style={[GlobalStyles.header, GlobalStyles.label]}
id='login_mfa.enterToken'
defaultMessage="To complete the sign in process, please enter a token from your smartphone's authenticator"
/>
<ErrorText error={this.state.error}/>
<TextInputWithLocalizedPlaceholder
ref={this.inputRef}
value={this.state.token}
onChangeText={this.handleInput}
onSubmitEditing={this.submit}
style={GlobalStyles.inputBox}
autoCapitalize='none'
autoCorrect={false}
keyboardType='numeric'
placeholder={{id: 'login_mfa.token', defaultMessage: 'MFA Token'}}
returnKeyType='go'
underlineColorAndroid='transparent'
/>
<Button
onPress={this.submit}
loading={false}
containerStyle={GlobalStyles.signupButton}
>
<FormattedText
style={GlobalStyles.signupButtonText}
id='mobile.components.select_server_view.proceed'
defaultMessage='Proceed'
/>
</Button>
</View>
</TouchableWithoutFeedback>
</KeyboardLayout>
);
}
}

View file

@ -5,8 +5,6 @@ import React, {Component} from 'react';
import {
Image,
Keyboard,
KeyboardAvoidingView,
Platform,
TouchableWithoutFeedback,
View
} from 'react-native';
@ -14,6 +12,7 @@ import {
import Button from 'react-native-button';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
import {GlobalStyles} from 'app/styles';
@ -49,58 +48,50 @@ export default class SelectServer extends Component {
};
render() {
const scene = (
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
<Image
source={logo}
/>
<FormattedText
style={[GlobalStyles.header, GlobalStyles.label]}
id='mobile.components.select_server_view.enterServerUrl'
defaultMessage='Enter Server URL'
/>
<TextInputWithLocalizedPlaceholder
ref={this.inputRef}
value={this.props.serverUrl}
onChangeText={this.props.actions.handleServerUrlChanged}
onSubmitEditing={this.onClick}
style={GlobalStyles.inputBox}
autoCapitalize='none'
autoCorrect={false}
keyboardType='url'
placeholder={{id: 'mobile.components.select_server_view.siteUrlPlaceholder', defaultMessage: 'https://mattermost.example.com'}}
returnKeyType='go'
underlineColorAndroid='transparent'
/>
<Button
onPress={this.onClick}
loading={this.props.server.loading}
containerStyle={GlobalStyles.signupButton}
>
<FormattedText
style={GlobalStyles.signupButtonText}
id='mobile.components.select_server_view.proceed'
defaultMessage='Proceed'
/>
</Button>
<ErrorText error={this.props.server.error}/>
</View>
</TouchableWithoutFeedback>
);
if (Platform.OS === 'android') {
return scene;
}
return (
<KeyboardAvoidingView
<KeyboardLayout
behavior='padding'
style={{flex: 1}}
keyboardVerticalOffset={0}
>
{scene}
</KeyboardAvoidingView>
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
<Image
source={logo}
/>
<FormattedText
style={[GlobalStyles.header, GlobalStyles.label]}
id='mobile.components.select_server_view.enterServerUrl'
defaultMessage='Enter Server URL'
/>
<TextInputWithLocalizedPlaceholder
ref={this.inputRef}
value={this.props.serverUrl}
onChangeText={this.props.actions.handleServerUrlChanged}
onSubmitEditing={this.onClick}
style={GlobalStyles.inputBox}
autoCapitalize='none'
autoCorrect={false}
keyboardType='url'
placeholder={{id: 'mobile.components.select_server_view.siteUrlPlaceholder', defaultMessage: 'https://mattermost.example.com'}}
returnKeyType='go'
underlineColorAndroid='transparent'
/>
<Button
onPress={this.onClick}
loading={this.props.server.loading}
containerStyle={GlobalStyles.signupButton}
>
<FormattedText
style={GlobalStyles.signupButtonText}
id='mobile.components.select_server_view.proceed'
defaultMessage='Proceed'
/>
</Button>
<ErrorText error={this.props.server.error}/>
</View>
</TouchableWithoutFeedback>
</KeyboardLayout>
);
}
}

View file

@ -2,14 +2,9 @@
// See License.txt for license information.
import React from 'react';
import {
KeyboardAvoidingView,
Platform,
StatusBar,
StyleSheet,
View
} from 'react-native';
import {StatusBar, StyleSheet} from 'react-native';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import PostList from 'app/components/post_list';
import PostTextbox from 'app/components/post_textbox';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@ -40,57 +35,25 @@ export default class Thread extends React.Component {
this.props.actions.handleCommentDraftChanged(this.props.rootId, value);
};
renderAndroid = (children) => {
const style = getStyle(this.props.theme);
return (
<View style={style.container}>
{children}
</View>
);
};
render() {
const style = getStyle(this.props.theme);
const status = (
<StatusBar
key='statusBar'
barStyle='light-content'
/>
);
const postList = (
<PostList
key='postList'
posts={this.props.posts}
/>
);
const postTextBox = (
<PostTextbox
key='postTextBox'
rootId={this.props.rootId}
value={this.props.draft}
teamId={this.props.teamId}
channelId={this.props.channelId}
onChangeText={this.handleDraftChanged}
/>
);
if (Platform.OS === 'android') {
return this.renderAndroid([status, postList, postTextBox]);
}
return (
<KeyboardAvoidingView
<KeyboardLayout
behavior='padding'
style={style.container}
keyboardVerticalOffset={65}
>
{status}
{postList}
{postTextBox}
</KeyboardAvoidingView>
<StatusBar barStyle='light-content'/>
<PostList posts={this.props.posts}/>
<PostTextbox
rootId={this.props.rootId}
value={this.props.draft}
teamId={this.props.teamId}
channelId={this.props.channelId}
onChangeText={this.handleDraftChanged}
/>
</KeyboardLayout>
);
}
}