This commit is contained in:
Diego Mello 2020-03-26 20:30:14 -03:00
parent 50e7d7dcf4
commit f0408b3bcf
6 changed files with 211 additions and 91 deletions

View File

@ -44,7 +44,7 @@ export default class Button extends React.PureComponent {
render() {
const {
title, type, onPress, disabled, backgroundColor, loading, style, theme, ...otherProps
title, type, onPress, disabled, backgroundColor, color, loading, style, theme, ...otherProps
} = this.props;
const isPrimary = type === 'primary';
return (
@ -68,7 +68,8 @@ export default class Button extends React.PureComponent {
<Text
style={[
styles.text,
{ color: isPrimary ? themes[theme].buttonText : themes[theme].bodyText }
{ color: isPrimary ? themes[theme].buttonText : themes[theme].bodyText },
color && { color }
]}
>
{title}

View File

@ -13,10 +13,16 @@ import { isTablet } from '../utils/deviceInfo';
const styles = StyleSheet.create({
scrollView: {
height: '100%'
minHeight: '100%'
}
});
export const FormContainerInner = ({ children }) => (
<View style={[sharedStyles.container, isTablet && sharedStyles.tabletScreenContent]}>
{children}
</View>
);
const FormContainer = ({ children, theme }) => (
<KeyboardView
style={{ backgroundColor: themes[theme].backgroundColor }}
@ -26,11 +32,11 @@ const FormContainer = ({ children, theme }) => (
<StatusBar theme={theme} />
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
<SafeAreaView style={sharedStyles.container} forceInset={{ top: 'never' }}>
<View style={[sharedStyles.container, isTablet && sharedStyles.tabletScreenContent]}>
{children}
</View>
{/* <View style={[sharedStyles.container, isTablet && sharedStyles.tabletScreenContent]}> */}
{children}
<AppVersion theme={theme} />
{/* </View> */}
</SafeAreaView>
<AppVersion theme={theme} />
</ScrollView>
</KeyboardView>
);

View File

@ -21,6 +21,9 @@ import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import { themedHeader } from '../utils/navigation';
import { isTablet } from '../utils/deviceInfo';
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import OnboardingSeparator from '../containers/OnboardingSeparator';
import TextInput from '../containers/TextInput';
const styles = StyleSheet.create({
container: {
@ -36,7 +39,6 @@ const styles = StyleSheet.create({
},
serviceButtonContainer: {
borderRadius: 2,
borderWidth: 1,
width: '100%',
height: 48,
flexDirection: 'row',
@ -89,7 +91,20 @@ const styles = StyleSheet.create({
},
inverted: {
transform: [{ scaleY: -1 }]
}
},
title: {
...sharedStyles.textBold,
fontSize: 22
},
inputContainer: {
marginTop: 24,
marginBottom: 32
},
bottomContainer: {
flexDirection: 'column',
alignItems: 'center',
marginBottom: 32
},
});
const SERVICE_HEIGHT = 58;
@ -123,7 +138,11 @@ class LoginSignupView extends React.Component {
super(props);
this.state = {
collapsed: true,
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT)
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT),
user: '',
password: '',
code: '',
showTOTP: false
};
const { Site_Name } = this.props;
this.setTitle(Site_Name);
@ -348,6 +367,16 @@ class LoginSignupView extends React.Component {
return oauthProviders[name];
}
valid = () => {
const {
user, password, code, showTOTP
} = this.state;
if (showTOTP) {
return code.trim();
}
return user.trim() && password.trim();
}
renderServicesSeparator = () => {
const { collapsed } = this.state;
const {
@ -356,21 +385,30 @@ class LoginSignupView extends React.Component {
const { length } = Object.values(services);
if (length > 3 && Accounts_ShowFormLogin && Accounts_RegistrationForm) {
// return (
// <View style={styles.servicesTogglerContainer}>
// <View style={[styles.separatorLine, styles.separatorLineLeft, { backgroundColor: themes[theme].auxiliaryText }]} />
// <BorderlessButton onPress={this.toggleServices}>
// <Image source={{ uri: 'options' }} style={[styles.servicesToggler, !collapsed && styles.inverted]} />
// </BorderlessButton>
// <View style={[styles.separatorLine, styles.separatorLineRight, { backgroundColor: themes[theme].auxiliaryText }]} />
// </View>
// );
return (
<View style={styles.servicesTogglerContainer}>
<View style={[styles.separatorLine, styles.separatorLineLeft, { backgroundColor: themes[theme].auxiliaryText }]} />
<BorderlessButton onPress={this.toggleServices}>
<Image source={{ uri: 'options' }} style={[styles.servicesToggler, !collapsed && styles.inverted]} />
</BorderlessButton>
<View style={[styles.separatorLine, styles.separatorLineRight, { backgroundColor: themes[theme].auxiliaryText }]} />
</View>
<>
<Button
title='More options'
type='secondary'
onPress={this.toggleServices}
theme={theme}
style={{ marginBottom: 0 }}
color={themes[theme].actionTintColor}
/>
<OnboardingSeparator theme={theme} />
</>
);
}
return (
<View style={styles.separatorContainer}>
<View style={styles.separatorLine} />
</View>
);
return <OnboardingSeparator theme={theme} />;
}
renderItem = (service) => {
@ -412,14 +450,19 @@ class LoginSignupView extends React.Component {
</>
);
}
const backgroundColor = isSaml && service.buttonColor ? service.buttonColor : themes[theme].chatComponentBackground;
return (
<Touch
key={service.name}
onPress={onPress}
style={[styles.serviceButton, isSaml && { backgroundColor: service.buttonColor }]}
style={[styles.serviceButton, { backgroundColor }]}
theme={theme}
activeOpacity={0.5}
underlayColor={themes[theme].buttonText}
>
<View style={[styles.serviceButtonContainer, { borderColor: themes[theme].borderColor }]}>
<View style={styles.serviceButtonContainer}>
{service.authType === 'oauth' ? <Image source={{ uri: icon }} style={styles.serviceIcon} /> : null}
<Text style={[styles.serviceText, { color: themes[theme].titleText }]}>{buttonText}</Text>
</View>
@ -482,31 +525,89 @@ class LoginSignupView extends React.Component {
);
}
renderUserForm = () => {
const {
Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, Accounts_PasswordReset, Accounts_RegistrationForm, Accounts_RegistrationForm_LinkReplacementText, isFetching, theme
} = this.props;
return (
<>
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Login')}</Text>
<TextInput
label='Email or username'
containerStyle={styles.inputContainer}
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username_or_email')}
keyboardType='email-address'
returnKeyType='next'
iconLeft='at'
onChangeText={value => this.setState({ user: value })}
onSubmitEditing={() => { this.passwordInput.focus(); }}
testID='login-view-email'
textContentType='username'
autoCompleteType='username'
theme={theme}
/>
<TextInput
label='Password'
containerStyle={styles.inputContainer}
inputRef={(e) => { this.passwordInput = e; }}
placeholder={Accounts_PasswordPlaceholder || I18n.t('Password')}
returnKeyType='send'
iconLeft='key'
secureTextEntry
onSubmitEditing={this.submit}
onChangeText={value => this.setState({ password: value })}
testID='login-view-password'
textContentType='password'
autoCompleteType='password'
theme={theme}
/>
<Button
title={I18n.t('Login')}
type='primary'
onPress={this.submit}
testID='login-view-submit'
loading={isFetching}
disabled={!this.valid()}
theme={theme}
/>
{Accounts_PasswordReset && (
<Button
title={I18n.t('Forgot_password')}
type='secondary'
onPress={this.forgotPassword}
testID='login-view-forgot-password'
theme={theme}
/>
)}
{Accounts_RegistrationForm === 'Public' ? (
<View style={styles.bottomContainer}>
<Text style={[styles.dontHaveAccount, { color: themes[theme].auxiliaryText }]}>{I18n.t('Dont_Have_An_Account')}</Text>
<Text
style={[styles.createAccount, { color: themes[theme].actionTintColor }]}
onPress={this.register}
testID='login-view-register'
>{I18n.t('Create_account')}
</Text>
</View>
) : (<Text style={[styles.registerDisabled, { color: themes[theme].auxiliaryText }]}>{Accounts_RegistrationForm_LinkReplacementText}</Text>)}
</>
);
}
render() {
const { showTOTP } = this.state;
const { theme } = this.props;
return (
<SafeAreaView
testID='welcome-view'
forceInset={{ vertical: 'never' }}
style={[styles.safeArea, { backgroundColor: themes[theme].backgroundColor }]}
>
<ScrollView
style={[
sharedStyles.containerScrollView,
sharedStyles.container,
styles.container,
{ backgroundColor: themes[theme].backgroundColor },
isTablet && sharedStyles.tabletScreenContent
]}
{...scrollPersistTaps}
>
<StatusBar theme={theme} />
<FormContainer theme={theme}>
<FormContainerInner>
{this.renderServices()}
{this.renderServicesSeparator()}
{this.renderLogin()}
{this.renderRegister()}
</ScrollView>
</SafeAreaView>
{/* {this.renderLogin()}
{this.renderRegister()} */}
{!showTOTP ? this.renderUserForm() : null}
{showTOTP ? this.renderTOTP() : null}
</FormContainerInner>
</FormContainer>
);
}
}
@ -520,7 +621,16 @@ const mapStateToProps = state => ({
Accounts_ShowFormLogin: state.settings.Accounts_ShowFormLogin,
Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm,
Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText,
services: state.login.services
services: state.login.services,
isFetching: state.login.isFetching,
failure: state.login.failure,
error: state.login.error && state.login.error.data,
// Site_Name: state.settings.Site_Name,
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder,
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder,
// Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm,
// Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText,
Accounts_PasswordReset: state.settings.Accounts_PasswordReset
});
export default connect(mapStateToProps)(withTheme(LoginSignupView));

View File

@ -18,7 +18,7 @@ import sharedStyles from './Styles';
import Button from '../containers/Button';
import TextInput from '../containers/TextInput';
import OnboardingSeparator from '../containers/OnboardingSeparator';
import FormContainer from '../containers/FormContainer';
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import I18n from '../i18n';
import { isIOS, isTablet } from '../utils/deviceInfo';
import { themes } from '../constants/colors';
@ -31,9 +31,7 @@ import { themedHeader } from '../utils/navigation';
const styles = StyleSheet.create({
title: {
...sharedStyles.textBold,
fontSize: 22,
letterSpacing: 0,
textAlign: 'auto'
fontSize: 22
},
inputContainer: {
marginTop: 24,
@ -264,7 +262,7 @@ class NewServerView extends React.Component {
const { text } = this.state;
return (
<FormContainer theme={theme}>
<View style={[sharedStyles.container, isTablet && { justifyContent: 'center' }]}>
<FormContainerInner>
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Join_your_workspace')}</Text>
<TextInput
label='Enter workspace URL'
@ -300,7 +298,7 @@ class NewServerView extends React.Component {
// loading={connecting} TODO: connecting to open
theme={theme}
/>
</View>
</FormContainerInner>
{ isIOS ? this.renderCertificatePicker() : null }
</FormContainer>
);

View File

@ -15,7 +15,7 @@ import { isTablet } from '../../utils/deviceInfo';
import EventEmitter from '../../utils/events';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import FormContainer from '../../containers/FormContainer';
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
class OnboardingView extends React.Component {
static navigationOptions = () => ({
@ -109,25 +109,27 @@ class OnboardingView extends React.Component {
const { theme } = this.props;
return (
<FormContainer theme={theme}>
<Image style={styles.onboarding} source={{ uri: 'logo' }} fadeDuration={0} />
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Onboarding_title')}</Text>
<Text style={[styles.subtitle, { color: themes[theme].controlText }]}>{I18n.t('Onboarding_subtitle')}</Text>
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_description')}</Text>
<View style={[styles.buttonsContainer]}>
<Button
title={I18n.t('Onboarding_join_workspace')}
type='primary'
onPress={this.connectServer}
theme={theme}
/>
<Button
title={I18n.t('Create_a_new_workspace')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.createWorkspace}
theme={theme}
/>
</View>
<FormContainerInner>
<Image style={styles.onboarding} source={{ uri: 'logo' }} fadeDuration={0} />
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Onboarding_title')}</Text>
<Text style={[styles.subtitle, { color: themes[theme].controlText }]}>{I18n.t('Onboarding_subtitle')}</Text>
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_description')}</Text>
<View style={[styles.buttonsContainer]}>
<Button
title={I18n.t('Onboarding_join_workspace')}
type='primary'
onPress={this.connectServer}
theme={theme}
/>
<Button
title={I18n.t('Create_a_new_workspace')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.createWorkspace}
theme={theme}
/>
</View>
</FormContainerInner>
</FormContainer>
);
}

View File

@ -10,11 +10,12 @@ import { appStart as appStartAction } from '../../actions';
import I18n from '../../i18n';
import Button from '../../containers/Button';
import styles from './styles';
import sharedStyles from '../Styles';
import { isTablet } from '../../utils/deviceInfo';
import EventEmitter from '../../utils/events';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import FormContainer from '../../containers/FormContainer';
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
import { themedHeader } from '../../utils/navigation';
import ServerAvatar from './ServerAvatar';
@ -37,7 +38,7 @@ class WorkspaceView extends React.Component {
login = () => {
const { navigation } = this.props;
navigation.navigate('LoginView');
navigation.navigate('LoginSignupView');
}
register = () => {
@ -49,24 +50,26 @@ class WorkspaceView extends React.Component {
const { theme, Site_Name, Site_Url, Assets_favicon_512, server } = this.props;
return (
<FormContainer theme={theme}>
<View style={{ alignItems: 'center' }}>
<ServerAvatar theme={theme} url={server} image={Assets_favicon_512 && Assets_favicon_512.defaultUrl} />
<Text style={[styles.serverName, { color: themes[theme].titleText }]}>{Site_Name}</Text>
<Text style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}>{Site_Url}</Text>
</View>
<Button
title={I18n.t('Login')}
type='primary'
onPress={this.login}
theme={theme}
/>
<Button
title={I18n.t('Create_account')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.register}
theme={theme}
/>
<FormContainerInner>
<View style={{ alignItems: 'center' }}>
<ServerAvatar theme={theme} url={server} image={Assets_favicon_512 && Assets_favicon_512.defaultUrl} />
<Text style={[styles.serverName, { color: themes[theme].titleText }]}>{Site_Name}</Text>
<Text style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}>{Site_Url}</Text>
</View>
<Button
title={I18n.t('Login')}
type='primary'
onPress={this.login}
theme={theme}
/>
<Button
title={I18n.t('Create_account')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.register}
theme={theme}
/>
</FormContainerInner>
</FormContainer>
);
}