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",
|
"password": "Password",
|
||||||
"passwordError": "Please enter a password",
|
"passwordError": "Please enter a password",
|
||||||
"resetPassword": "Forgot Password",
|
"resetPassword": "Forgot Password",
|
||||||
"whyAccountTitle": "Why have an account?",
|
"mascotDialog": {
|
||||||
"whyAccountSub": "What can you do wth an account",
|
"title": "Why have 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!",
|
"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.",
|
||||||
"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!",
|
"button": "OK"
|
||||||
"noAccount": "No Account? Go to the Amicale's building during open hours to create one."
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"title": "Profile",
|
"title": "Profile",
|
||||||
|
|
|
@ -186,11 +186,11 @@
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
"passwordError": "Merci d'entrer un mot de passe",
|
"passwordError": "Merci d'entrer un mot de passe",
|
||||||
"resetPassword": "Mdp oublié",
|
"resetPassword": "Mdp oublié",
|
||||||
"whyAccountTitle": "Un compte ?",
|
"mascotDialog": {
|
||||||
"whyAccountSub": "Ce qu'un compte t'apporte",
|
"title": "Un compte ?",
|
||||||
"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 !",
|
"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.",
|
||||||
"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 !",
|
"button": "Dac"
|
||||||
"noAccount": "Pas de compte ? Passe à l'Amicale pendant une perm pour en créer un."
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"title": "Profil",
|
"title": "Profil",
|
||||||
|
|
|
@ -84,6 +84,11 @@ export default class AsyncStorageManager {
|
||||||
default: '1',
|
default: '1',
|
||||||
current: '',
|
current: '',
|
||||||
},
|
},
|
||||||
|
loginShowBanner: {
|
||||||
|
key: 'loginShowBanner',
|
||||||
|
default: '1',
|
||||||
|
current: '',
|
||||||
|
},
|
||||||
proxiwashWatchedMachines: {
|
proxiwashWatchedMachines: {
|
||||||
key: 'proxiwashWatchedMachines',
|
key: 'proxiwashWatchedMachines',
|
||||||
default: '[]',
|
default: '[]',
|
||||||
|
|
|
@ -90,14 +90,8 @@ function MainStackComponent(props: { createTabNavigator: () => React.Node }) {
|
||||||
title: i18n.t("screens.game.title"),
|
title: i18n.t("screens.game.title"),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<MainStack.Screen
|
{createScreenCollapsibleStack("login", MainStack, LoginScreen, i18n.t('screens.login.title'),
|
||||||
name="login"
|
true, {headerTintColor: "#fff"}, 'transparent')}
|
||||||
component={LoginScreen}
|
|
||||||
options={{
|
|
||||||
title: i18n.t('screens.login.title'),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{getWebsiteStack("website", MainStack, WebsiteScreen, "")}
|
{getWebsiteStack("website", MainStack, WebsiteScreen, "")}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Animated, KeyboardAvoidingView, StyleSheet, View} from "react-native";
|
import {Animated, Dimensions, Image, KeyboardAvoidingView, StyleSheet, View} from "react-native";
|
||||||
import {Avatar, Button, Card, HelperText, Paragraph, TextInput, withTheme} from 'react-native-paper';
|
import {Button, Card, HelperText, TextInput, withTheme} from 'react-native-paper';
|
||||||
import ConnectionManager from "../../managers/ConnectionManager";
|
import ConnectionManager from "../../managers/ConnectionManager";
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import ErrorDialog from "../../components/Dialogs/ErrorDialog";
|
import ErrorDialog from "../../components/Dialogs/ErrorDialog";
|
||||||
import {withCollapsible} from "../../utils/withCollapsible";
|
import {withCollapsible} from "../../utils/withCollapsible";
|
||||||
import {Collapsible} from "react-navigation-collapsible";
|
import {Collapsible} from "react-navigation-collapsible";
|
||||||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
|
||||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import {StackNavigationProp} from "@react-navigation/stack";
|
||||||
import AvailableWebsites from "../../constants/AvailableWebsites";
|
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 = {
|
type Props = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
|
@ -29,6 +31,7 @@ type State = {
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
dialogVisible: boolean,
|
dialogVisible: boolean,
|
||||||
dialogError: number,
|
dialogError: number,
|
||||||
|
mascotDialogVisible: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ICON_AMICALE = require('../../../assets/amicale.png');
|
const ICON_AMICALE = require('../../../assets/amicale.png');
|
||||||
|
@ -47,11 +50,13 @@ class LoginScreen extends React.Component<Props, State> {
|
||||||
loading: false,
|
loading: false,
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
dialogError: 0,
|
dialogError: 0,
|
||||||
|
mascotDialogVisible: AsyncStorageManager.getInstance().preferences.loginShowBanner.current === "1"
|
||||||
};
|
};
|
||||||
|
|
||||||
onEmailChange: (value: string) => null;
|
onEmailChange: (value: string) => null;
|
||||||
onPasswordChange: (value: string) => null;
|
onPasswordChange: (value: string) => null;
|
||||||
passwordInputRef: { current: null | TextInput };
|
passwordInputRef: { current: null | TextInput };
|
||||||
|
windowHeight: number;
|
||||||
|
|
||||||
nextScreen: string | null;
|
nextScreen: string | null;
|
||||||
|
|
||||||
|
@ -61,6 +66,7 @@ class LoginScreen extends React.Component<Props, State> {
|
||||||
this.onEmailChange = this.onInputChange.bind(this, true);
|
this.onEmailChange = this.onInputChange.bind(this, true);
|
||||||
this.onPasswordChange = this.onInputChange.bind(this, false);
|
this.onPasswordChange = this.onInputChange.bind(this, false);
|
||||||
this.props.navigation.addListener('focus', this.onScreenFocus);
|
this.props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
|
this.windowHeight = Dimensions.get('window').height;
|
||||||
}
|
}
|
||||||
|
|
||||||
onScreenFocus = () => {
|
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
|
* 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
|
* 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
|
* 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() {
|
getMainCard() {
|
||||||
return (
|
return (
|
||||||
<Card style={styles.card}>
|
<View style={styles.card}>
|
||||||
<Card.Title
|
<Card.Title
|
||||||
title={i18n.t("screens.login.title")}
|
title={i18n.t("screens.login.title")}
|
||||||
|
titleStyle={{color: "#fff"}}
|
||||||
subtitle={i18n.t("screens.login.subtitle")}
|
subtitle={i18n.t("screens.login.subtitle")}
|
||||||
left={(props) => <Avatar.Image
|
subtitleStyle={{color: "#fff"}}
|
||||||
|
left={(props) => <Image
|
||||||
{...props}
|
{...props}
|
||||||
source={ICON_AMICALE}
|
source={ICON_AMICALE}
|
||||||
style={{backgroundColor: 'transparent'}}/>}
|
style={{
|
||||||
|
width: props.size,
|
||||||
|
height: props.size,
|
||||||
|
}}/>}
|
||||||
/>
|
/>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
{this.getFormInput()}
|
{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
|
<Button
|
||||||
icon="send"
|
icon="send"
|
||||||
mode="contained"
|
mode="contained"
|
||||||
|
@ -304,50 +339,35 @@ class LoginScreen extends React.Component<Props, State> {
|
||||||
style={{marginLeft: 'auto'}}>
|
style={{marginLeft: 'auto'}}>
|
||||||
{i18n.t("screens.login.title")}
|
{i18n.t("screens.login.title")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</Card.Actions>
|
</Card.Actions>
|
||||||
<Card.Actions>
|
<Card.Actions>
|
||||||
<Button
|
<Button
|
||||||
icon="help-circle"
|
icon="help-circle"
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={this.onResetPasswordClick}
|
onPress={this.showMascotDialog}
|
||||||
style={{marginLeft: 'auto'}}>
|
style={{
|
||||||
{i18n.t("screens.login.resetPassword")}
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
}}>
|
||||||
|
{i18n.t("screens.login.mascotDialog.title")}
|
||||||
</Button>
|
</Button>
|
||||||
</Card.Actions>
|
</Card.Actions>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</View>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
|
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
|
||||||
return (
|
return (
|
||||||
|
<LinearGradient
|
||||||
|
style={{
|
||||||
|
height: "100%"
|
||||||
|
}}
|
||||||
|
colors={['#9e0d18', '#530209']}
|
||||||
|
start={{x: 0, y: 0.1}}
|
||||||
|
end={{x: 0.1, y: 1}}>
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
behavior={"height"}
|
behavior={"height"}
|
||||||
contentContainerStyle={styles.container}
|
contentContainerStyle={styles.container}
|
||||||
|
@ -357,16 +377,29 @@ class LoginScreen extends React.Component<Props, State> {
|
||||||
>
|
>
|
||||||
<Animated.ScrollView
|
<Animated.ScrollView
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
contentContainerStyle={{
|
|
||||||
paddingTop: containerPaddingTop,
|
|
||||||
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20
|
|
||||||
}}
|
|
||||||
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
|
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
|
||||||
|
style={{flex: 1}}
|
||||||
>
|
>
|
||||||
<View>
|
<View style={{height: this.windowHeight - containerPaddingTop}}>
|
||||||
{this.getMainCard()}
|
{this.getMainCard()}
|
||||||
{this.getSecondaryCard()}
|
|
||||||
</View>
|
</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
|
<ErrorDialog
|
||||||
visible={this.state.dialogVisible}
|
visible={this.state.dialogVisible}
|
||||||
onDismiss={this.hideErrorDialog}
|
onDismiss={this.hideErrorDialog}
|
||||||
|
@ -374,6 +407,7 @@ class LoginScreen extends React.Component<Props, State> {
|
||||||
/>
|
/>
|
||||||
</Animated.ScrollView>
|
</Animated.ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
|
</LinearGradient>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,11 +415,10 @@ class LoginScreen extends React.Component<Props, State> {
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
margin: 10,
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
fontSize: 36,
|
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.
|
* 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.
|
* 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 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}
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export function createScreenCollapsibleStack(
|
export function createScreenCollapsibleStack(
|
||||||
|
@ -26,7 +27,8 @@ export function createScreenCollapsibleStack(
|
||||||
component: React.Node,
|
component: React.Node,
|
||||||
title: string,
|
title: string,
|
||||||
useNativeDriver?: boolean,
|
useNativeDriver?: boolean,
|
||||||
options?: StackNavigationOptions) {
|
options?: StackNavigationOptions,
|
||||||
|
headerColor?: string) {
|
||||||
const {colors} = useTheme();
|
const {colors} = useTheme();
|
||||||
const screenOptions = options != null ? options : {};
|
const screenOptions = options != null ? options : {};
|
||||||
return createCollapsibleStack(
|
return createCollapsibleStack(
|
||||||
|
@ -36,13 +38,13 @@ export function createScreenCollapsibleStack(
|
||||||
options={{
|
options={{
|
||||||
title: title,
|
title: title,
|
||||||
headerStyle: {
|
headerStyle: {
|
||||||
backgroundColor: colors.surface,
|
backgroundColor: headerColor!=null ? headerColor :colors.surface,
|
||||||
},
|
},
|
||||||
...screenOptions,
|
...screenOptions,
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
collapsedColor: colors.surface,
|
collapsedColor: headerColor!=null ? headerColor :colors.surface,
|
||||||
useNativeDriver: useNativeDriver != null ? useNativeDriver : true, // native driver does not work with webview
|
useNativeDriver: useNativeDriver != null ? useNativeDriver : true, // native driver does not work with webview
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue