forked from vergnet/application-amicale
Improved login screen
This commit is contained in:
parent
434d8b6565
commit
f8d148d7ce
6 changed files with 122 additions and 87 deletions
|
@ -186,11 +186,12 @@
|
|||
"password": "Password",
|
||||
"passwordError": "Please enter a password",
|
||||
"resetPassword": "Forgot Password",
|
||||
"whyAccountTitle": "Why have an account?",
|
||||
"whyAccountSub": "What can you do wth an account",
|
||||
"whyAccountParagraph": "An Amicale account allows you to take part in several activities around campus. You can join a club, or even create your own!",
|
||||
"whyAccountParagraph2": "Logging into your Amicale account on the app will allow you to see all available clubs on the campus, vote for the upcoming elections, and more to come!",
|
||||
"noAccount": "No Account? Go to the Amicale's building during open hours to create one."
|
||||
"mascotDialog": {
|
||||
"title": "Why have an account?",
|
||||
"message": "An Amicale account allows you to take part in several activities around campus. You can join a club, or even create your own!\n\nLogging into your Amicale account on the app will allow you to see all available clubs on the campus, vote for the upcoming elections, and more to come!\n\nNo Account? Go to the Amicale's building during open hours to create one.",
|
||||
"button": "OK"
|
||||
}
|
||||
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profile",
|
||||
|
|
|
@ -186,11 +186,11 @@
|
|||
"password": "Mot de passe",
|
||||
"passwordError": "Merci d'entrer un mot de passe",
|
||||
"resetPassword": "Mdp oublié",
|
||||
"whyAccountTitle": "Un compte ?",
|
||||
"whyAccountSub": "Ce qu'un compte t'apporte",
|
||||
"whyAccountParagraph": "Un compte Amicale te donne la possibilité de participer à diverses activités sur le campus. tu peux rejoindre des clubs ou même créer le tiens !",
|
||||
"whyAccountParagraph2": "Te connecter à ton compte Amicale sur l'appli te permettra de voir tous les clubs en activité, de réserver du matériel, de voter pour les prochaines élections, et plus à venir !",
|
||||
"noAccount": "Pas de compte ? Passe à l'Amicale pendant une perm pour en créer un."
|
||||
"mascotDialog": {
|
||||
"title": "Un compte ?",
|
||||
"message": "Un compte Amicale te donne la possibilité de participer à diverses activités sur le campus. tu peux rejoindre des clubs ou même créer le tiens !\n\nTe connecter à ton compte Amicale sur l'appli te permettra de voir tous les clubs en activité, de réserver du matériel, de voter pour les prochaines élections, et plus à venir !\n\nPas de compte ? Passe à l'Amicale pendant une perm pour en créer un.",
|
||||
"button": "Dac"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profil",
|
||||
|
|
|
@ -84,6 +84,11 @@ export default class AsyncStorageManager {
|
|||
default: '1',
|
||||
current: '',
|
||||
},
|
||||
loginShowBanner: {
|
||||
key: 'loginShowBanner',
|
||||
default: '1',
|
||||
current: '',
|
||||
},
|
||||
proxiwashWatchedMachines: {
|
||||
key: 'proxiwashWatchedMachines',
|
||||
default: '[]',
|
||||
|
|
|
@ -90,14 +90,8 @@ function MainStackComponent(props: { createTabNavigator: () => React.Node }) {
|
|||
title: i18n.t("screens.game.title"),
|
||||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name="login"
|
||||
component={LoginScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.login.title'),
|
||||
}}
|
||||
/>
|
||||
|
||||
{createScreenCollapsibleStack("login", MainStack, LoginScreen, i18n.t('screens.login.title'),
|
||||
true, {headerTintColor: "#fff"}, 'transparent')}
|
||||
{getWebsiteStack("website", MainStack, WebsiteScreen, "")}
|
||||
|
||||
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated, KeyboardAvoidingView, StyleSheet, View} from "react-native";
|
||||
import {Avatar, Button, Card, HelperText, Paragraph, TextInput, withTheme} from 'react-native-paper';
|
||||
import {Animated, Dimensions, Image, KeyboardAvoidingView, StyleSheet, View} from "react-native";
|
||||
import {Button, Card, HelperText, TextInput, withTheme} from 'react-native-paper';
|
||||
import ConnectionManager from "../../managers/ConnectionManager";
|
||||
import i18n from 'i18n-js';
|
||||
import ErrorDialog from "../../components/Dialogs/ErrorDialog";
|
||||
import {withCollapsible} from "../../utils/withCollapsible";
|
||||
import {Collapsible} from "react-navigation-collapsible";
|
||||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import AvailableWebsites from "../../constants/AvailableWebsites";
|
||||
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
|
||||
import MascotPopup from "../../components/Mascot/MascotPopup";
|
||||
import LinearGradient from "react-native-linear-gradient";
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
|
@ -29,6 +31,7 @@ type State = {
|
|||
loading: boolean,
|
||||
dialogVisible: boolean,
|
||||
dialogError: number,
|
||||
mascotDialogVisible: boolean,
|
||||
}
|
||||
|
||||
const ICON_AMICALE = require('../../../assets/amicale.png');
|
||||
|
@ -47,11 +50,13 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
loading: false,
|
||||
dialogVisible: false,
|
||||
dialogError: 0,
|
||||
mascotDialogVisible: AsyncStorageManager.getInstance().preferences.loginShowBanner.current === "1"
|
||||
};
|
||||
|
||||
onEmailChange: (value: string) => null;
|
||||
onPasswordChange: (value: string) => null;
|
||||
passwordInputRef: { current: null | TextInput };
|
||||
windowHeight: number;
|
||||
|
||||
nextScreen: string | null;
|
||||
|
||||
|
@ -61,6 +66,7 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
this.onEmailChange = this.onInputChange.bind(this, true);
|
||||
this.onPasswordChange = this.onInputChange.bind(this, false);
|
||||
this.props.navigation.addListener('focus', this.onScreenFocus);
|
||||
this.windowHeight = Dimensions.get('window').height;
|
||||
}
|
||||
|
||||
onScreenFocus = () => {
|
||||
|
@ -79,6 +85,18 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
hideMascotDialog = () => {
|
||||
AsyncStorageManager.getInstance().savePref(
|
||||
AsyncStorageManager.getInstance().preferences.loginShowBanner.key,
|
||||
'0'
|
||||
);
|
||||
this.setState({mascotDialogVisible: false})
|
||||
};
|
||||
|
||||
showMascotDialog = () => {
|
||||
this.setState({mascotDialogVisible: true})
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows an error dialog with the corresponding login error
|
||||
*
|
||||
|
@ -111,7 +129,11 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
/**
|
||||
* Navigates to the Amicale website screen with the reset password link as navigation parameters
|
||||
*/
|
||||
onResetPasswordClick = () => this.props.navigation.navigate("website", {host: AvailableWebsites.websites.AMICALE, path: RESET_PASSWORD_PATH, title: i18n.t('screens.websites.amicale')});
|
||||
onResetPasswordClick = () => this.props.navigation.navigate("website", {
|
||||
host: AvailableWebsites.websites.AMICALE,
|
||||
path: RESET_PASSWORD_PATH,
|
||||
title: i18n.t('screens.websites.amicale')
|
||||
});
|
||||
|
||||
/**
|
||||
* The user has unfocused the input, his email is ready to be validated
|
||||
|
@ -283,18 +305,31 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
*/
|
||||
getMainCard() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<View style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t("screens.login.title")}
|
||||
titleStyle={{color: "#fff"}}
|
||||
subtitle={i18n.t("screens.login.subtitle")}
|
||||
left={(props) => <Avatar.Image
|
||||
subtitleStyle={{color: "#fff"}}
|
||||
left={(props) => <Image
|
||||
{...props}
|
||||
source={ICON_AMICALE}
|
||||
style={{backgroundColor: 'transparent'}}/>}
|
||||
style={{
|
||||
width: props.size,
|
||||
height: props.size,
|
||||
}}/>}
|
||||
/>
|
||||
<Card.Content>
|
||||
{this.getFormInput()}
|
||||
<Card.Actions>
|
||||
<Card.Actions style={{flexWrap: "wrap"}}>
|
||||
<Button
|
||||
icon="lock-question"
|
||||
mode="contained"
|
||||
onPress={this.onResetPasswordClick}
|
||||
color={this.props.theme.colors.warning}
|
||||
style={{marginRight: 'auto', marginBottom: 20}}>
|
||||
{i18n.t("screens.login.resetPassword")}
|
||||
</Button>
|
||||
<Button
|
||||
icon="send"
|
||||
mode="contained"
|
||||
|
@ -304,76 +339,75 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
style={{marginLeft: 'auto'}}>
|
||||
{i18n.t("screens.login.title")}
|
||||
</Button>
|
||||
|
||||
</Card.Actions>
|
||||
<Card.Actions>
|
||||
<Button
|
||||
icon="help-circle"
|
||||
mode="contained"
|
||||
onPress={this.onResetPasswordClick}
|
||||
style={{marginLeft: 'auto'}}>
|
||||
{i18n.t("screens.login.resetPassword")}
|
||||
onPress={this.showMascotDialog}
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}>
|
||||
{i18n.t("screens.login.mascotDialog.title")}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the card containing the information about the Amicale account
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getSecondaryCard() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t("screens.login.whyAccountTitle")}
|
||||
subtitle={i18n.t("screens.login.whyAccountSub")}
|
||||
left={(props) => <Avatar.Icon
|
||||
{...props}
|
||||
icon={"help"}
|
||||
color={this.props.theme.colors.primary}
|
||||
style={{backgroundColor: 'transparent'}}/>}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Paragraph>{i18n.t("screens.login.whyAccountParagraph")}</Paragraph>
|
||||
<Paragraph>{i18n.t("screens.login.whyAccountParagraph2")}</Paragraph>
|
||||
<Paragraph>{i18n.t("screens.login.noAccount")}</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={"height"}
|
||||
contentContainerStyle={styles.container}
|
||||
style={styles.container}
|
||||
enabled
|
||||
keyboardVerticalOffset={100}
|
||||
>
|
||||
<Animated.ScrollView
|
||||
onScroll={onScroll}
|
||||
contentContainerStyle={{
|
||||
paddingTop: containerPaddingTop,
|
||||
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20
|
||||
}}
|
||||
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
|
||||
<LinearGradient
|
||||
style={{
|
||||
height: "100%"
|
||||
}}
|
||||
colors={['#9e0d18', '#530209']}
|
||||
start={{x: 0, y: 0.1}}
|
||||
end={{x: 0.1, y: 1}}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={"height"}
|
||||
contentContainerStyle={styles.container}
|
||||
style={styles.container}
|
||||
enabled
|
||||
keyboardVerticalOffset={100}
|
||||
>
|
||||
<View>
|
||||
{this.getMainCard()}
|
||||
{this.getSecondaryCard()}
|
||||
</View>
|
||||
<ErrorDialog
|
||||
visible={this.state.dialogVisible}
|
||||
onDismiss={this.hideErrorDialog}
|
||||
errorCode={this.state.dialogError}
|
||||
/>
|
||||
</Animated.ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
<Animated.ScrollView
|
||||
onScroll={onScroll}
|
||||
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
|
||||
style={{flex: 1}}
|
||||
>
|
||||
<View style={{height: this.windowHeight - containerPaddingTop}}>
|
||||
{this.getMainCard()}
|
||||
</View>
|
||||
|
||||
|
||||
<MascotPopup
|
||||
visible={this.state.mascotDialogVisible}
|
||||
title={i18n.t("screens.login.mascotDialog.title")}
|
||||
message={i18n.t("screens.login.mascotDialog.message")}
|
||||
icon={"help"}
|
||||
buttons={{
|
||||
action: null,
|
||||
cancel: {
|
||||
message: i18n.t("screens.login.mascotDialog.button"),
|
||||
icon: "check",
|
||||
onPress: this.hideMascotDialog,
|
||||
}
|
||||
}}
|
||||
emotion={MASCOT_STYLE.NORMAL}
|
||||
/>
|
||||
<ErrorDialog
|
||||
visible={this.state.dialogVisible}
|
||||
onDismiss={this.hideErrorDialog}
|
||||
errorCode={this.state.dialogError}
|
||||
/>
|
||||
</Animated.ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</LinearGradient>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -381,11 +415,10 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
card: {
|
||||
margin: 10,
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
},
|
||||
header: {
|
||||
fontSize: 36,
|
||||
|
|
|
@ -18,6 +18,7 @@ import StackNavigator, {StackNavigationOptions} from "@react-navigation/stack";
|
|||
* Set to false if the screen uses a webview as this component does not support native driver.
|
||||
* In all other cases, set it to true for increase performance.
|
||||
* @param options Screen options to use, or null if no options are necessary.
|
||||
* @param headerColor The color of the header. Uses default color if not specified
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function createScreenCollapsibleStack(
|
||||
|
@ -26,7 +27,8 @@ export function createScreenCollapsibleStack(
|
|||
component: React.Node,
|
||||
title: string,
|
||||
useNativeDriver?: boolean,
|
||||
options?: StackNavigationOptions) {
|
||||
options?: StackNavigationOptions,
|
||||
headerColor?: string) {
|
||||
const {colors} = useTheme();
|
||||
const screenOptions = options != null ? options : {};
|
||||
return createCollapsibleStack(
|
||||
|
@ -36,13 +38,13 @@ export function createScreenCollapsibleStack(
|
|||
options={{
|
||||
title: title,
|
||||
headerStyle: {
|
||||
backgroundColor: colors.surface,
|
||||
backgroundColor: headerColor!=null ? headerColor :colors.surface,
|
||||
},
|
||||
...screenOptions,
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
collapsedColor: colors.surface,
|
||||
collapsedColor: headerColor!=null ? headerColor :colors.surface,
|
||||
useNativeDriver: useNativeDriver != null ? useNativeDriver : true, // native driver does not work with webview
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue