Compare commits

..

4 commits

7 changed files with 245 additions and 82 deletions

37
components/AlertDialog.js Normal file
View file

@ -0,0 +1,37 @@
import * as React from 'react';
import {Button, Dialog, Paragraph, Portal, withTheme} from 'react-native-paper';
type Props = {
navigation: Object,
visible: boolean,
onDismiss: Function,
title: string,
message: string,
}
class AlertDialog extends React.PureComponent<Props> {
constructor(props) {
super(props);
}
render() {
return (
<Portal>
<Dialog
visible={this.props.visible}
onDismiss={this.props.onDismiss}>
<Dialog.Title>{this.props.title}</Dialog.Title>
<Dialog.Content>
<Paragraph>{this.props.message}</Paragraph>
</Dialog.Content>
<Dialog.Actions>
<Button onPress={this.props.onDismiss}>OK</Button>
</Dialog.Actions>
</Dialog>
</Portal>
);
}
}
export default withTheme(AlertDialog);

View file

@ -0,0 +1,81 @@
import * as React from 'react';
import {ActivityIndicator, Button, Dialog, Paragraph, Portal, withTheme} from 'react-native-paper';
import ConnectionManager from "../managers/ConnectionManager";
import i18n from 'i18n-js';
type Props = {
navigation: Object,
visible: boolean,
onDismiss: Function,
}
type State = {
loading: boolean,
}
class LogoutDialog extends React.PureComponent<Props, State> {
colors: Object;
state = {
loading: false,
};
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
onClickAccept = () => {
this.setState({loading: true});
ConnectionManager.getInstance().disconnect()
.then(() => {
this.props.onDismiss();
this.setState({loading: false});
this.props.navigation.reset({
index: 0,
routes: [{name: 'Main'}],
});
});
};
onDismiss = () => {
if (!this.state.loading)
this.props.onDismiss();
};
render() {
return (
<Portal>
<Dialog
visible={this.props.visible}
onDismiss={this.onDismiss}>
<Dialog.Title>
{this.state.loading
? i18n.t("dialog.disconnect.titleLoading")
: i18n.t("dialog.disconnect.title")}
</Dialog.Title>
<Dialog.Content>
{this.state.loading
? <ActivityIndicator
animating={true}
size={'large'}
color={this.colors.primary}/>
: <Paragraph>{i18n.t("dialog.disconnect.message")}</Paragraph>
}
</Dialog.Content>
{this.state.loading
? null
: <Dialog.Actions>
<Button onPress={this.onDismiss} style={{marginRight: 10}}>{i18n.t("dialog.cancel")}</Button>
<Button onPress={this.onClickAccept}>{i18n.t("dialog.yes")}</Button>
</Dialog.Actions>
}
</Dialog>
</Portal>
);
}
}
export default withTheme(LogoutDialog);

View file

@ -1,13 +1,14 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Alert, Dimensions, FlatList, Image, Platform, StyleSheet, View} from 'react-native'; import {Dimensions, FlatList, Image, Platform, StyleSheet, View,} from 'react-native';
import i18n from "i18n-js"; import i18n from "i18n-js";
import {openBrowser} from "../utils/WebBrowser"; import {openBrowser} from "../utils/WebBrowser";
import SidebarDivider from "./SidebarDivider"; import SidebarDivider from "./SidebarDivider";
import SidebarItem from "./SidebarItem"; import SidebarItem from "./SidebarItem";
import {TouchableRipple, withTheme} from "react-native-paper"; import {TouchableRipple, withTheme} from "react-native-paper";
import ConnectionManager from "../managers/ConnectionManager"; import ConnectionManager from "../managers/ConnectionManager";
import LogoutDialog from "./LogoutDialog";
const deviceWidth = Dimensions.get("window").width; const deviceWidth = Dimensions.get("window").width;
@ -20,6 +21,7 @@ type Props = {
type State = { type State = {
active: string, active: string,
isLoggedIn: boolean, isLoggedIn: boolean,
dialogVisible: boolean,
}; };
/** /**
@ -47,7 +49,7 @@ class SideBar extends React.PureComponent<Props, State> {
icon: "home", icon: "home",
}, },
{ {
name: "AMICALE", name: i18n.t('sidenav.divider4'),
route: "Divider4" route: "Divider4"
}, },
{ {
@ -65,7 +67,7 @@ class SideBar extends React.PureComponent<Props, State> {
{ {
name: i18n.t('screens.logout'), name: i18n.t('screens.logout'),
route: 'disconnect', route: 'disconnect',
action: () => this.onClickDisconnect(), action: this.showDisconnectDialog,
icon: "logout", icon: "logout",
onlyWhenLoggedIn: true, onlyWhenLoggedIn: true,
}, },
@ -149,31 +151,15 @@ class SideBar extends React.PureComponent<Props, State> {
this.state = { this.state = {
active: 'Home', active: 'Home',
isLoggedIn: false, isLoggedIn: false,
dialogVisible: false,
}; };
ConnectionManager.getInstance().isLoggedIn() ConnectionManager.getInstance().isLoggedIn().then(data => undefined).catch(error => undefined);
} }
onClickDisconnect() { showDisconnectDialog = () => this.setState({ dialogVisible: true });
Alert.alert(
'DISCONNECT', hideDisconnectDialog = () => this.setState({ dialogVisible: false });
'DISCONNECT?',
[
{
text: 'YES', onPress: () => {
ConnectionManager.getInstance().disconnect()
.then(() => {
this.props.navigation.reset({
index: 0,
routes: [{name: 'Main'}],
});
});
}
},
{text: 'NO', undefined},
],
{cancelable: false},
);
}
onLoginStateChange(isLoggedIn: boolean) { onLoginStateChange(isLoggedIn: boolean) {
this.setState({isLoggedIn: isLoggedIn}); this.setState({isLoggedIn: isLoggedIn});
@ -250,6 +236,11 @@ class SideBar extends React.PureComponent<Props, State> {
keyExtractor={this.listKeyExtractor} keyExtractor={this.listKeyExtractor}
renderItem={this.getRenderItem} renderItem={this.getRenderItem}
/> />
<LogoutDialog
{...this.props}
visible={this.state.dialogVisible}
onDismiss={this.hideDisconnectDialog}
/>
</View> </View>
); );
} }

View file

@ -1,19 +1,12 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import { import {Keyboard, KeyboardAvoidingView, ScrollView, StyleSheet, TouchableWithoutFeedback, View} from "react-native";
Alert,
Keyboard,
KeyboardAvoidingView,
ScrollView,
StyleSheet,
TouchableWithoutFeedback,
View
} from "react-native";
import {Avatar, Button, Card, HelperText, Text, TextInput, withTheme} from 'react-native-paper'; import {Avatar, Button, Card, HelperText, Text, TextInput, withTheme} from 'react-native-paper';
import ConnectionManager, {ERROR_TYPE} from "../../managers/ConnectionManager"; import ConnectionManager, {ERROR_TYPE} from "../../managers/ConnectionManager";
import {openBrowser} from "../../utils/WebBrowser"; import {openBrowser} from "../../utils/WebBrowser";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import AlertDialog from "../../components/AlertDialog";
type Props = { type Props = {
navigation: Object, navigation: Object,
@ -25,6 +18,9 @@ type State = {
isEmailValidated: boolean, isEmailValidated: boolean,
isPasswordValidated: boolean, isPasswordValidated: boolean,
loading: boolean, loading: boolean,
dialogVisible: boolean,
dialogTitle: string,
dialogMessage: string,
} }
const ICON_AMICALE = require('../../assets/amicale.png'); const ICON_AMICALE = require('../../assets/amicale.png');
@ -41,6 +37,9 @@ class LoginScreen extends React.Component<Props, State> {
isEmailValidated: false, isEmailValidated: false,
isPasswordValidated: false, isPasswordValidated: false,
loading: false, loading: false,
dialogVisible: false,
dialogTitle: '',
dialogMessage: '',
}; };
colors: Object; colors: Object;
@ -67,6 +66,15 @@ class LoginScreen extends React.Component<Props, State> {
this.colors = props.theme.colors; this.colors = props.theme.colors;
} }
showErrorDialog = (title: string, message: string) =>
this.setState({
dialogTitle: title,
dialogMessage: message,
dialogVisible: true
});
hideErrorDialog = () => this.setState({ dialogVisible: false });
onResetPasswordClick() { onResetPasswordClick() {
openBrowser(RESET_PASSWORD_LINK, this.colors.primary); openBrowser(RESET_PASSWORD_LINK, this.colors.primary);
} }
@ -96,7 +104,7 @@ class LoginScreen extends React.Component<Props, State> {
} }
shouldEnableLogin() { shouldEnableLogin() {
return this.isEmailValid() && this.isPasswordValid(); return this.isEmailValid() && this.isPasswordValid() && !this.state.loading;
} }
onInputChange(isEmail: boolean, value: string) { onInputChange(isEmail: boolean, value: string) {
@ -157,7 +165,7 @@ class LoginScreen extends React.Component<Props, State> {
message = i18n.t("loginScreen.errors.unknown"); message = i18n.t("loginScreen.errors.unknown");
break; break;
} }
Alert.alert(title, message); this.showErrorDialog(title, message);
} }
getFormInput() { getFormInput() {
@ -279,6 +287,13 @@ class LoginScreen extends React.Component<Props, State> {
{this.getSecondaryCard()} {this.getSecondaryCard()}
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
<AlertDialog
{...this.props}
visible={this.state.dialogVisible}
title={this.state.dialogTitle}
message={this.state.dialogMessage}
onDismiss={this.hideErrorDialog}
/>
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
); );

View file

@ -1,22 +1,26 @@
import * as React from 'react'; import * as React from 'react';
import {FlatList, StyleSheet} from "react-native"; import {FlatList, StyleSheet, View} from "react-native";
import {Avatar, Button, Card, Divider, List, withTheme} from 'react-native-paper'; import {Avatar, Button, Card, Divider, List, withTheme} from 'react-native-paper';
import AuthenticatedScreen from "../../components/AuthenticatedScreen"; import AuthenticatedScreen from "../../components/AuthenticatedScreen";
import {openBrowser} from "../../utils/WebBrowser"; import {openBrowser} from "../../utils/WebBrowser";
import ConnectionManager from "../../managers/ConnectionManager";
import HeaderButton from "../../components/HeaderButton"; import HeaderButton from "../../components/HeaderButton";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import LogoutDialog from "../../components/LogoutDialog";
type Props = { type Props = {
navigation: Object, navigation: Object,
theme: Object, theme: Object,
} }
type State = {} type State = {
dialogVisible: boolean,
}
class ProfileScreen extends React.Component<Props, State> { class ProfileScreen extends React.Component<Props, State> {
state = {}; state = {
dialogVisible: false,
};
colors: Object; colors: Object;
@ -27,11 +31,10 @@ class ProfileScreen extends React.Component<Props, State> {
constructor(props) { constructor(props) {
super(props); super(props);
this.colors = props.theme.colors; this.colors = props.theme.colors;
this.onClickDisconnect = this.onClickDisconnect.bind(this);
this.flatListData = [ this.flatListData = [
{id: 0}, {id: '0'},
{id: 1}, {id: '1'},
{id: 2}, {id: '2'},
] ]
} }
@ -42,38 +45,40 @@ class ProfileScreen extends React.Component<Props, State> {
}); });
} }
getHeaderButtons() { showDisconnectDialog = () => this.setState({ dialogVisible: true });
return <HeaderButton icon={'logout'} onPress={this.onClickDisconnect}/>;
}
onClickDisconnect() { hideDisconnectDialog = () => this.setState({ dialogVisible: false });
ConnectionManager.getInstance().disconnect()
.then(() => { getHeaderButtons() {
this.props.navigation.reset({ return <HeaderButton icon={'logout'} onPress={this.showDisconnectDialog}/>;
index: 0,
routes: [{name: 'Main'}],
});
});
} }
getScreen(data: Object) { getScreen(data: Object) {
this.data = data; this.data = data;
return ( return (
<FlatList <View>
renderItem={item => this.getRenderItem(item)} <FlatList
keyExtractor={item => item.id} renderItem={item => this.getRenderItem(item)}
data={this.flatListData} keyExtractor={item => item.id}
/> data={this.flatListData}
/>
<LogoutDialog
{...this.props}
visible={this.state.dialogVisible}
onDismiss={this.hideDisconnectDialog}
/>
</View>
) )
} }
getRenderItem({item}: Object) { getRenderItem({item}: Object) {
switch (item.id) { switch (item.id) {
case 0: case '0':
return this.getPersonalCard(); return this.getPersonalCard();
case 1: case '1':
return this.getClubCard(); return this.getClubCard();
case 2: case '2':
return this.getMembershipCar(); return this.getMembershipCar();
} }
} }
@ -96,22 +101,10 @@ class ProfileScreen extends React.Component<Props, State> {
<Divider/> <Divider/>
<List.Section> <List.Section>
<List.Subheader>{i18n.t("profileScreen.personalInformation")}</List.Subheader> <List.Subheader>{i18n.t("profileScreen.personalInformation")}</List.Subheader>
<List.Item {this.getPersonalListItem(this.data.birthday, "cake-variant")}
title={this.getFieldValue(this.data.birthday)} {this.getPersonalListItem(this.data.phone, "phone")}
left={props => <List.Icon {...props} icon="cake-variant"/>} {this.getPersonalListItem(this.data.email, "email")}
/> {this.getPersonalListItem(this.data.branch, "school")}
<List.Item
title={this.getFieldValue(this.data.phone)}
left={props => <List.Icon {...props} icon="phone"/>}
/>
<List.Item
title={this.getFieldValue(this.data.email)}
left={props => <List.Icon {...props} icon="email"/>}
/>
<List.Item
title={this.getFieldValue(this.data.branch)}
left={props => <List.Icon {...props} icon="school"/>}
/>
</List.Section> </List.Section>
<Divider/> <Divider/>
<Card.Actions> <Card.Actions>
@ -203,12 +196,36 @@ class ProfileScreen extends React.Component<Props, State> {
); );
} }
isFieldAvailable(field: ?string) {
return field !== null;
}
getFieldValue(field: ?string) { getFieldValue(field: ?string) {
return field !== null return this.isFieldAvailable(field)
? field ? field
: i18n.t("profileScreen.noData"); : i18n.t("profileScreen.noData");
} }
getFieldColor(field: ?string) {
return this.isFieldAvailable(field)
? this.colors.text
: this.colors.textDisabled;
}
getPersonalListItem(field: ?string, icon: string) {
return (
<List.Item
title={this.getFieldValue(field)}
left={props => <List.Icon
{...props}
icon={icon}
color={this.getFieldColor(field)}
/>}
titleStyle={{color: this.getFieldColor(field)}}
/>
);
}
render() { render() {
return ( return (
<AuthenticatedScreen <AuthenticatedScreen

View file

@ -20,7 +20,8 @@
"sidenav": { "sidenav": {
"divider1": "Student websites", "divider1": "Student websites",
"divider2": "Services", "divider2": "Services",
"divider3": "Personalisation" "divider3": "Personalisation",
"divider4": "Amicale"
}, },
"intro": { "intro": {
"slide1": { "slide1": {
@ -241,6 +242,16 @@
"unknown": "Unknown error, please contact support." "unknown": "Unknown error, please contact support."
} }
}, },
"dialog": {
"ok": "OK",
"yes": "Yes",
"cancel": "Cancel",
"disconnect": {
"title": "Disconnect",
"titleLoading": "Disconnecting...",
"message": "Are you sure you want to disconnect from your Amicale account?"
}
},
"general": { "general": {
"loading": "Loading...", "loading": "Loading...",
"networkError": "Unable to contact servers. Make sure you are connected to Internet." "networkError": "Unable to contact servers. Make sure you are connected to Internet."

View file

@ -20,7 +20,8 @@
"sidenav": { "sidenav": {
"divider1": "Sites étudiants", "divider1": "Sites étudiants",
"divider2": "Services", "divider2": "Services",
"divider3": "Personnalisation" "divider3": "Personnalisation",
"divider4": "Amicale"
}, },
"intro": { "intro": {
"slide1": { "slide1": {
@ -242,6 +243,16 @@
"unknown": "Erreur inconnue, merci de contacter le support." "unknown": "Erreur inconnue, merci de contacter le support."
} }
}, },
"dialog": {
"ok": "OK",
"yes": "Oui",
"cancel": "Annuler",
"disconnect": {
"title": "Déconnexion",
"titleLoading": "Déconnexion...",
"message": "Voulez vous vraiment vous déconnecter de votre compte Amicale ??"
}
},
"general": { "general": {
"loading": "Chargement...", "loading": "Chargement...",
"networkError": "Impossible de contacter les serveurs. Assurez-vous d'être connecté à internet." "networkError": "Impossible de contacter les serveurs. Assurez-vous d'être connecté à internet."