|
@@ -1,18 +1,20 @@
|
1
|
1
|
// @flow
|
2
|
2
|
|
3
|
3
|
import * as React from 'react';
|
4
|
|
-import {Animated, KeyboardAvoidingView, StyleSheet, View} from "react-native";
|
5
|
|
-import {Avatar, Button, Card, HelperText, Paragraph, TextInput, withTheme} from 'react-native-paper';
|
|
4
|
+import {Animated, Dimensions, Image, KeyboardAvoidingView, StyleSheet, View} from "react-native";
|
|
5
|
+import {Button, Card, HelperText, TextInput, withTheme} from 'react-native-paper';
|
6
|
6
|
import ConnectionManager from "../../managers/ConnectionManager";
|
7
|
7
|
import i18n from 'i18n-js';
|
8
|
8
|
import ErrorDialog from "../../components/Dialogs/ErrorDialog";
|
9
|
9
|
import {withCollapsible} from "../../utils/withCollapsible";
|
10
|
10
|
import {Collapsible} from "react-navigation-collapsible";
|
11
|
|
-import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
12
|
11
|
import type {CustomTheme} from "../../managers/ThemeManager";
|
13
|
12
|
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
14
|
13
|
import {StackNavigationProp} from "@react-navigation/stack";
|
15
|
14
|
import AvailableWebsites from "../../constants/AvailableWebsites";
|
|
15
|
+import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
|
|
16
|
+import MascotPopup from "../../components/Mascot/MascotPopup";
|
|
17
|
+import LinearGradient from "react-native-linear-gradient";
|
16
|
18
|
|
17
|
19
|
type Props = {
|
18
|
20
|
navigation: StackNavigationProp,
|
|
@@ -29,6 +31,7 @@ type State = {
|
29
|
31
|
loading: boolean,
|
30
|
32
|
dialogVisible: boolean,
|
31
|
33
|
dialogError: number,
|
|
34
|
+ mascotDialogVisible: boolean,
|
32
|
35
|
}
|
33
|
36
|
|
34
|
37
|
const ICON_AMICALE = require('../../../assets/amicale.png');
|
|
@@ -47,11 +50,13 @@ class LoginScreen extends React.Component<Props, State> {
|
47
|
50
|
loading: false,
|
48
|
51
|
dialogVisible: false,
|
49
|
52
|
dialogError: 0,
|
|
53
|
+ mascotDialogVisible: AsyncStorageManager.getInstance().preferences.loginShowBanner.current === "1"
|
50
|
54
|
};
|
51
|
55
|
|
52
|
56
|
onEmailChange: (value: string) => null;
|
53
|
57
|
onPasswordChange: (value: string) => null;
|
54
|
58
|
passwordInputRef: { current: null | TextInput };
|
|
59
|
+ windowHeight: number;
|
55
|
60
|
|
56
|
61
|
nextScreen: string | null;
|
57
|
62
|
|
|
@@ -61,6 +66,7 @@ class LoginScreen extends React.Component<Props, State> {
|
61
|
66
|
this.onEmailChange = this.onInputChange.bind(this, true);
|
62
|
67
|
this.onPasswordChange = this.onInputChange.bind(this, false);
|
63
|
68
|
this.props.navigation.addListener('focus', this.onScreenFocus);
|
|
69
|
+ this.windowHeight = Dimensions.get('window').height;
|
64
|
70
|
}
|
65
|
71
|
|
66
|
72
|
onScreenFocus = () => {
|
|
@@ -79,6 +85,18 @@ class LoginScreen extends React.Component<Props, State> {
|
79
|
85
|
}
|
80
|
86
|
}
|
81
|
87
|
|
|
88
|
+ hideMascotDialog = () => {
|
|
89
|
+ AsyncStorageManager.getInstance().savePref(
|
|
90
|
+ AsyncStorageManager.getInstance().preferences.loginShowBanner.key,
|
|
91
|
+ '0'
|
|
92
|
+ );
|
|
93
|
+ this.setState({mascotDialogVisible: false})
|
|
94
|
+ };
|
|
95
|
+
|
|
96
|
+ showMascotDialog = () => {
|
|
97
|
+ this.setState({mascotDialogVisible: true})
|
|
98
|
+ };
|
|
99
|
+
|
82
|
100
|
/**
|
83
|
101
|
* Shows an error dialog with the corresponding login error
|
84
|
102
|
*
|
|
@@ -111,7 +129,11 @@ class LoginScreen extends React.Component<Props, State> {
|
111
|
129
|
/**
|
112
|
130
|
* Navigates to the Amicale website screen with the reset password link as navigation parameters
|
113
|
131
|
*/
|
114
|
|
- onResetPasswordClick = () => this.props.navigation.navigate("website", {host: AvailableWebsites.websites.AMICALE, path: RESET_PASSWORD_PATH, title: i18n.t('screens.websites.amicale')});
|
|
132
|
+ onResetPasswordClick = () => this.props.navigation.navigate("website", {
|
|
133
|
+ host: AvailableWebsites.websites.AMICALE,
|
|
134
|
+ path: RESET_PASSWORD_PATH,
|
|
135
|
+ title: i18n.t('screens.websites.amicale')
|
|
136
|
+ });
|
115
|
137
|
|
116
|
138
|
/**
|
117
|
139
|
* The user has unfocused the input, his email is ready to be validated
|
|
@@ -283,18 +305,31 @@ class LoginScreen extends React.Component<Props, State> {
|
283
|
305
|
*/
|
284
|
306
|
getMainCard() {
|
285
|
307
|
return (
|
286
|
|
- <Card style={styles.card}>
|
|
308
|
+ <View style={styles.card}>
|
287
|
309
|
<Card.Title
|
288
|
310
|
title={i18n.t("screens.login.title")}
|
|
311
|
+ titleStyle={{color: "#fff"}}
|
289
|
312
|
subtitle={i18n.t("screens.login.subtitle")}
|
290
|
|
- left={(props) => <Avatar.Image
|
|
313
|
+ subtitleStyle={{color: "#fff"}}
|
|
314
|
+ left={(props) => <Image
|
291
|
315
|
{...props}
|
292
|
316
|
source={ICON_AMICALE}
|
293
|
|
- style={{backgroundColor: 'transparent'}}/>}
|
|
317
|
+ style={{
|
|
318
|
+ width: props.size,
|
|
319
|
+ height: props.size,
|
|
320
|
+ }}/>}
|
294
|
321
|
/>
|
295
|
322
|
<Card.Content>
|
296
|
323
|
{this.getFormInput()}
|
297
|
|
- <Card.Actions>
|
|
324
|
+ <Card.Actions style={{flexWrap: "wrap"}}>
|
|
325
|
+ <Button
|
|
326
|
+ icon="lock-question"
|
|
327
|
+ mode="contained"
|
|
328
|
+ onPress={this.onResetPasswordClick}
|
|
329
|
+ color={this.props.theme.colors.warning}
|
|
330
|
+ style={{marginRight: 'auto', marginBottom: 20}}>
|
|
331
|
+ {i18n.t("screens.login.resetPassword")}
|
|
332
|
+ </Button>
|
298
|
333
|
<Button
|
299
|
334
|
icon="send"
|
300
|
335
|
mode="contained"
|
|
@@ -304,76 +339,75 @@ class LoginScreen extends React.Component<Props, State> {
|
304
|
339
|
style={{marginLeft: 'auto'}}>
|
305
|
340
|
{i18n.t("screens.login.title")}
|
306
|
341
|
</Button>
|
|
342
|
+
|
307
|
343
|
</Card.Actions>
|
308
|
344
|
<Card.Actions>
|
309
|
345
|
<Button
|
310
|
346
|
icon="help-circle"
|
311
|
347
|
mode="contained"
|
312
|
|
- onPress={this.onResetPasswordClick}
|
313
|
|
- style={{marginLeft: 'auto'}}>
|
314
|
|
- {i18n.t("screens.login.resetPassword")}
|
|
348
|
+ onPress={this.showMascotDialog}
|
|
349
|
+ style={{
|
|
350
|
+ marginLeft: 'auto',
|
|
351
|
+ marginRight: 'auto',
|
|
352
|
+ }}>
|
|
353
|
+ {i18n.t("screens.login.mascotDialog.title")}
|
315
|
354
|
</Button>
|
316
|
355
|
</Card.Actions>
|
317
|
356
|
</Card.Content>
|
318
|
|
- </Card>
|
319
|
|
- );
|
320
|
|
- }
|
321
|
|
-
|
322
|
|
- /**
|
323
|
|
- * Gets the card containing the information about the Amicale account
|
324
|
|
- *
|
325
|
|
- * @returns {*}
|
326
|
|
- */
|
327
|
|
- getSecondaryCard() {
|
328
|
|
- return (
|
329
|
|
- <Card style={styles.card}>
|
330
|
|
- <Card.Title
|
331
|
|
- title={i18n.t("screens.login.whyAccountTitle")}
|
332
|
|
- subtitle={i18n.t("screens.login.whyAccountSub")}
|
333
|
|
- left={(props) => <Avatar.Icon
|
334
|
|
- {...props}
|
335
|
|
- icon={"help"}
|
336
|
|
- color={this.props.theme.colors.primary}
|
337
|
|
- style={{backgroundColor: 'transparent'}}/>}
|
338
|
|
- />
|
339
|
|
- <Card.Content>
|
340
|
|
- <Paragraph>{i18n.t("screens.login.whyAccountParagraph")}</Paragraph>
|
341
|
|
- <Paragraph>{i18n.t("screens.login.whyAccountParagraph2")}</Paragraph>
|
342
|
|
- <Paragraph>{i18n.t("screens.login.noAccount")}</Paragraph>
|
343
|
|
- </Card.Content>
|
344
|
|
- </Card>
|
|
357
|
+ </View>
|
345
|
358
|
);
|
346
|
359
|
}
|
347
|
360
|
|
348
|
361
|
render() {
|
349
|
362
|
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
|
350
|
363
|
return (
|
351
|
|
- <KeyboardAvoidingView
|
352
|
|
- behavior={"height"}
|
353
|
|
- contentContainerStyle={styles.container}
|
354
|
|
- style={styles.container}
|
355
|
|
- enabled
|
356
|
|
- keyboardVerticalOffset={100}
|
357
|
|
- >
|
358
|
|
- <Animated.ScrollView
|
359
|
|
- onScroll={onScroll}
|
360
|
|
- contentContainerStyle={{
|
361
|
|
- paddingTop: containerPaddingTop,
|
362
|
|
- paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20
|
363
|
|
- }}
|
364
|
|
- scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
|
|
364
|
+ <LinearGradient
|
|
365
|
+ style={{
|
|
366
|
+ height: "100%"
|
|
367
|
+ }}
|
|
368
|
+ colors={['#9e0d18', '#530209']}
|
|
369
|
+ start={{x: 0, y: 0.1}}
|
|
370
|
+ end={{x: 0.1, y: 1}}>
|
|
371
|
+ <KeyboardAvoidingView
|
|
372
|
+ behavior={"height"}
|
|
373
|
+ contentContainerStyle={styles.container}
|
|
374
|
+ style={styles.container}
|
|
375
|
+ enabled
|
|
376
|
+ keyboardVerticalOffset={100}
|
365
|
377
|
>
|
366
|
|
- <View>
|
367
|
|
- {this.getMainCard()}
|
368
|
|
- {this.getSecondaryCard()}
|
369
|
|
- </View>
|
370
|
|
- <ErrorDialog
|
371
|
|
- visible={this.state.dialogVisible}
|
372
|
|
- onDismiss={this.hideErrorDialog}
|
373
|
|
- errorCode={this.state.dialogError}
|
374
|
|
- />
|
375
|
|
- </Animated.ScrollView>
|
376
|
|
- </KeyboardAvoidingView>
|
|
378
|
+ <Animated.ScrollView
|
|
379
|
+ onScroll={onScroll}
|
|
380
|
+ scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
|
|
381
|
+ style={{flex: 1}}
|
|
382
|
+ >
|
|
383
|
+ <View style={{height: this.windowHeight - containerPaddingTop}}>
|
|
384
|
+ {this.getMainCard()}
|
|
385
|
+ </View>
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+ <MascotPopup
|
|
389
|
+ visible={this.state.mascotDialogVisible}
|
|
390
|
+ title={i18n.t("screens.login.mascotDialog.title")}
|
|
391
|
+ message={i18n.t("screens.login.mascotDialog.message")}
|
|
392
|
+ icon={"help"}
|
|
393
|
+ buttons={{
|
|
394
|
+ action: null,
|
|
395
|
+ cancel: {
|
|
396
|
+ message: i18n.t("screens.login.mascotDialog.button"),
|
|
397
|
+ icon: "check",
|
|
398
|
+ onPress: this.hideMascotDialog,
|
|
399
|
+ }
|
|
400
|
+ }}
|
|
401
|
+ emotion={MASCOT_STYLE.NORMAL}
|
|
402
|
+ />
|
|
403
|
+ <ErrorDialog
|
|
404
|
+ visible={this.state.dialogVisible}
|
|
405
|
+ onDismiss={this.hideErrorDialog}
|
|
406
|
+ errorCode={this.state.dialogError}
|
|
407
|
+ />
|
|
408
|
+ </Animated.ScrollView>
|
|
409
|
+ </KeyboardAvoidingView>
|
|
410
|
+ </LinearGradient>
|
377
|
411
|
);
|
378
|
412
|
}
|
379
|
413
|
}
|
|
@@ -381,11 +415,10 @@ class LoginScreen extends React.Component<Props, State> {
|
381
|
415
|
const styles = StyleSheet.create({
|
382
|
416
|
container: {
|
383
|
417
|
flex: 1,
|
384
|
|
- flexDirection: 'column',
|
385
|
|
- justifyContent: 'center',
|
386
|
418
|
},
|
387
|
419
|
card: {
|
388
|
|
- margin: 10,
|
|
420
|
+ marginTop: 'auto',
|
|
421
|
+ marginBottom: 'auto',
|
389
|
422
|
},
|
390
|
423
|
header: {
|
391
|
424
|
fontSize: 36,
|