From 0a64f5fcd74bae697033294d548aa05be2058e7f Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Wed, 5 Aug 2020 11:54:13 +0200 Subject: [PATCH] Improve Amicale screen components to match linter --- src/screens/Amicale/AmicaleContactScreen.js | 256 ++--- .../Amicale/Clubs/ClubDisplayScreen.js | 4 +- .../Equipment/EquipmentConfirmScreen.js | 4 +- .../Amicale/Equipment/EquipmentRentScreen.js | 4 +- src/screens/Amicale/LoginScreen.js | 831 +++++++++-------- src/screens/Amicale/ProfileScreen.js | 873 +++++++++--------- 6 files changed, 1028 insertions(+), 944 deletions(-) diff --git a/src/screens/Amicale/AmicaleContactScreen.js b/src/screens/Amicale/AmicaleContactScreen.js index a5cf607..6f5903a 100644 --- a/src/screens/Amicale/AmicaleContactScreen.js +++ b/src/screens/Amicale/AmicaleContactScreen.js @@ -4,137 +4,157 @@ import * as React from 'react'; import {FlatList, Image, Linking, View} from 'react-native'; import {Card, List, Text, withTheme} from 'react-native-paper'; import i18n from 'i18n-js'; -import type {MaterialCommunityIconsGlyphs} from "react-native-vector-icons/MaterialCommunityIcons"; -import CollapsibleFlatList from "../../components/Collapsible/CollapsibleFlatList"; +import type {MaterialCommunityIconsGlyphs} from 'react-native-vector-icons/MaterialCommunityIcons'; +import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; +import AMICALE_LOGO from '../../../assets/amicale.png'; -type Props = { +type DatasetItemType = { + name: string, + email: string, + icon: MaterialCommunityIconsGlyphs, }; -type DatasetItem = { - name: string, - email: string, - icon: MaterialCommunityIconsGlyphs, -} - /** * Class defining a planning event information page. */ -class AmicaleContactScreen extends React.Component { +class AmicaleContactScreen extends React.Component { + // Dataset containing information about contacts + CONTACT_DATASET: Array; - // Dataset containing information about contacts - CONTACT_DATASET: Array; + constructor() { + super(); + this.CONTACT_DATASET = [ + { + name: i18n.t('screens.amicaleAbout.roles.interSchools'), + email: 'inter.ecoles@amicale-insat.fr', + icon: 'share-variant', + }, + { + name: i18n.t('screens.amicaleAbout.roles.culture'), + email: 'culture@amicale-insat.fr', + icon: 'book', + }, + { + name: i18n.t('screens.amicaleAbout.roles.animation'), + email: 'animation@amicale-insat.fr', + icon: 'emoticon', + }, + { + name: i18n.t('screens.amicaleAbout.roles.clubs'), + email: 'clubs@amicale-insat.fr', + icon: 'account-group', + }, + { + name: i18n.t('screens.amicaleAbout.roles.event'), + email: 'evenements@amicale-insat.fr', + icon: 'calendar-range', + }, + { + name: i18n.t('screens.amicaleAbout.roles.tech'), + email: 'technique@amicale-insat.fr', + icon: 'cog', + }, + { + name: i18n.t('screens.amicaleAbout.roles.communication'), + email: 'amicale@amicale-insat.fr', + icon: 'comment-account', + }, + { + name: i18n.t('screens.amicaleAbout.roles.intraSchools'), + email: 'intra.ecoles@amicale-insat.fr', + icon: 'school', + }, + { + name: i18n.t('screens.amicaleAbout.roles.publicRelations'), + email: 'rp@amicale-insat.fr', + icon: 'account-tie', + }, + ]; + } - constructor(props: Props) { - super(props); - this.CONTACT_DATASET = [ - { - name: i18n.t("screens.amicaleAbout.roles.interSchools"), - email: "inter.ecoles@amicale-insat.fr", - icon: "share-variant" - }, - { - name: i18n.t("screens.amicaleAbout.roles.culture"), - email: "culture@amicale-insat.fr", - icon: "book" - }, - { - name: i18n.t("screens.amicaleAbout.roles.animation"), - email: "animation@amicale-insat.fr", - icon: "emoticon" - }, - { - name: i18n.t("screens.amicaleAbout.roles.clubs"), - email: "clubs@amicale-insat.fr", - icon: "account-group" - }, - { - name: i18n.t("screens.amicaleAbout.roles.event"), - email: "evenements@amicale-insat.fr", - icon: "calendar-range" - }, - { - name: i18n.t("screens.amicaleAbout.roles.tech"), - email: "technique@amicale-insat.fr", - icon: "cog" - }, - { - name: i18n.t("screens.amicaleAbout.roles.communication"), - email: "amicale@amicale-insat.fr", - icon: "comment-account" - }, - { - name: i18n.t("screens.amicaleAbout.roles.intraSchools"), - email: "intra.ecoles@amicale-insat.fr", - icon: "school" - }, - { - name: i18n.t("screens.amicaleAbout.roles.publicRelations"), - email: "rp@amicale-insat.fr", - icon: "account-tie" - }, - ]; - } + keyExtractor = (item: DatasetItemType): string => item.email; - keyExtractor = (item: DatasetItem) => item.email; + getChevronIcon = ({ + size, + color, + }: { + size: number, + color: string, + }): React.Node => ( + + ); - getChevronIcon = (props) => ; - - renderItem = ({item}: { item: DatasetItem }) => { - const onPress = () => Linking.openURL('mailto:' + item.email); - return } - right={this.getChevronIcon} - onPress={onPress} - /> + getRenderItem = ({item}: {item: DatasetItemType}): React.Node => { + const onPress = () => { + Linking.openURL(`mailto:${item.email}`); }; + return ( + ( + + )} + right={this.getChevronIcon} + onPress={onPress} + /> + ); + }; - getScreen = () => { - return ( - - - - - - } - /> - - {i18n.t("screens.amicaleAbout.message")} - {/*$FlowFixMe*/} - - - - - ); - }; - - render() { - return ( - { + return ( + + + + + + ( + + )} + /> + + {i18n.t('screens.amicaleAbout.message')} + - ); - } + + + + ); + }; + + render(): React.Node { + return ( + + ); + } } export default withTheme(AmicaleContactScreen); diff --git a/src/screens/Amicale/Clubs/ClubDisplayScreen.js b/src/screens/Amicale/Clubs/ClubDisplayScreen.js index 598597d..d4ee95b 100644 --- a/src/screens/Amicale/Clubs/ClubDisplayScreen.js +++ b/src/screens/Amicale/Clubs/ClubDisplayScreen.js @@ -17,7 +17,7 @@ import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen import CustomHTML from '../../../components/Overrides/CustomHTML'; import CustomTabBar from '../../../components/Tabbar/CustomTabBar'; import type {ClubCategoryType, ClubType} from './ClubListScreen'; -import type {CustomTheme} from '../../../managers/ThemeManager'; +import type {CustomThemeType} from '../../../managers/ThemeManager'; import {ERROR_TYPE} from '../../../utils/WebData'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import type {ApiGenericDataType} from '../../../utils/WebData'; @@ -32,7 +32,7 @@ type PropsType = { }, ... }, - theme: CustomTheme, + theme: CustomThemeType, }; const AMICALE_MAIL = 'clubs@amicale-insat.fr'; diff --git a/src/screens/Amicale/Equipment/EquipmentConfirmScreen.js b/src/screens/Amicale/Equipment/EquipmentConfirmScreen.js index 506391f..9028e0d 100644 --- a/src/screens/Amicale/Equipment/EquipmentConfirmScreen.js +++ b/src/screens/Amicale/Equipment/EquipmentConfirmScreen.js @@ -11,7 +11,7 @@ import { } from 'react-native-paper'; import {View} from 'react-native'; import i18n from 'i18n-js'; -import type {CustomTheme} from '../../../managers/ThemeManager'; +import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {DeviceType} from './EquipmentListScreen'; import {getRelativeDateString} from '../../../utils/EquipmentBooking'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; @@ -23,7 +23,7 @@ type PropsType = { dates: [string, string], }, }, - theme: CustomTheme, + theme: CustomThemeType, }; class EquipmentConfirmScreen extends React.Component { diff --git a/src/screens/Amicale/Equipment/EquipmentRentScreen.js b/src/screens/Amicale/Equipment/EquipmentRentScreen.js index 5c102b4..b4e9413 100644 --- a/src/screens/Amicale/Equipment/EquipmentRentScreen.js +++ b/src/screens/Amicale/Equipment/EquipmentRentScreen.js @@ -15,7 +15,7 @@ import * as Animatable from 'react-native-animatable'; import i18n from 'i18n-js'; import {CalendarList} from 'react-native-calendars'; import type {DeviceType} from './EquipmentListScreen'; -import type {CustomTheme} from '../../../managers/ThemeManager'; +import type {CustomThemeType} from '../../../managers/ThemeManager'; import LoadingConfirmDialog from '../../../components/Dialogs/LoadingConfirmDialog'; import ErrorDialog from '../../../components/Dialogs/ErrorDialog'; import { @@ -36,7 +36,7 @@ type PropsType = { item?: DeviceType, }, }, - theme: CustomTheme, + theme: CustomThemeType, }; export type MarkedDatesObjectType = { diff --git a/src/screens/Amicale/LoginScreen.js b/src/screens/Amicale/LoginScreen.js index c98a984..d7fa50c 100644 --- a/src/screens/Amicale/LoginScreen.js +++ b/src/screens/Amicale/LoginScreen.js @@ -1,417 +1,446 @@ // @flow import * as React from 'react'; -import {Image, KeyboardAvoidingView, StyleSheet, View} from "react-native"; -import {Button, Card, HelperText, TextInput, withTheme} from 'react-native-paper'; -import ConnectionManager from "../../managers/ConnectionManager"; +import {Image, KeyboardAvoidingView, StyleSheet, View} from 'react-native'; +import { + Button, + Card, + HelperText, + TextInput, + withTheme, +} from 'react-native-paper'; import i18n from 'i18n-js'; -import ErrorDialog from "../../components/Dialogs/ErrorDialog"; -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"; -import CollapsibleScrollView from "../../components/Collapsible/CollapsibleScrollView"; +import {StackNavigationProp} from '@react-navigation/stack'; +import LinearGradient from 'react-native-linear-gradient'; +import ConnectionManager from '../../managers/ConnectionManager'; +import ErrorDialog from '../../components/Dialogs/ErrorDialog'; +import type {CustomThemeType} from '../../managers/ThemeManager'; +import AsyncStorageManager from '../../managers/AsyncStorageManager'; +import AvailableWebsites from '../../constants/AvailableWebsites'; +import {MASCOT_STYLE} from '../../components/Mascot/Mascot'; +import MascotPopup from '../../components/Mascot/MascotPopup'; +import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView'; -type Props = { - navigation: StackNavigationProp, - route: { params: { nextScreen: string } }, - theme: CustomTheme -} +type PropsType = { + navigation: StackNavigationProp, + route: {params: {nextScreen: string}}, + theme: CustomThemeType, +}; -type State = { - email: string, - password: string, - isEmailValidated: boolean, - isPasswordValidated: boolean, - loading: boolean, - dialogVisible: boolean, - dialogError: number, - mascotDialogVisible: boolean, -} +type StateType = { + email: string, + password: string, + isEmailValidated: boolean, + isPasswordValidated: boolean, + loading: boolean, + dialogVisible: boolean, + dialogError: number, + mascotDialogVisible: boolean, +}; const ICON_AMICALE = require('../../../assets/amicale.png'); -const RESET_PASSWORD_PATH = "https://www.amicale-insat.fr/password/reset"; +const RESET_PASSWORD_PATH = 'https://www.amicale-insat.fr/password/reset'; const emailRegex = /^.+@.+\..+$/; -class LoginScreen extends React.Component { - - state = { - email: '', - password: '', - isEmailValidated: false, - isPasswordValidated: false, - loading: false, - dialogVisible: false, - dialogError: 0, - mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.loginShowBanner.key), - }; - - onEmailChange: (value: string) => null; - onPasswordChange: (value: string) => null; - passwordInputRef: { current: null | TextInput }; - - nextScreen: string | null; - - constructor(props) { - super(props); - this.passwordInputRef = React.createRef(); - this.onEmailChange = this.onInputChange.bind(this, true); - this.onPasswordChange = this.onInputChange.bind(this, false); - this.props.navigation.addListener('focus', this.onScreenFocus); - } - - onScreenFocus = () => { - this.handleNavigationParams(); - }; - - /** - * Saves the screen to navigate to after a successful login if one was provided in navigation parameters - */ - handleNavigationParams() { - if (this.props.route.params != null) { - if (this.props.route.params.nextScreen != null) - this.nextScreen = this.props.route.params.nextScreen; - else - this.nextScreen = null; - } - } - - hideMascotDialog = () => { - AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.loginShowBanner.key, false); - this.setState({mascotDialogVisible: false}) - }; - - showMascotDialog = () => { - this.setState({mascotDialogVisible: true}) - }; - - /** - * Shows an error dialog with the corresponding login error - * - * @param error The error given by the login request - */ - showErrorDialog = (error: number) => - this.setState({ - dialogVisible: true, - dialogError: error, - }); - - hideErrorDialog = () => this.setState({dialogVisible: false}); - - /** - * Navigates to the screen specified in navigation parameters or simply go back tha stack. - * Saves in user preferences to not show the login banner again. - */ - handleSuccess = () => { - // Do not show the home login banner again - AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.homeShowBanner.key, false); - if (this.nextScreen == null) - this.props.navigation.goBack(); - else - this.props.navigation.replace(this.nextScreen); - }; - - /** - * 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') - }); - - /** - * The user has unfocused the input, his email is ready to be validated - */ - validateEmail = () => this.setState({isEmailValidated: true}); - - /** - * Checks if the entered email is valid (matches the regex) - * - * @returns {boolean} - */ - isEmailValid() { - return emailRegex.test(this.state.email); - } - - /** - * Checks if we should tell the user his email is invalid. - * We should only show this if his email is invalid and has been checked when un-focusing the input - * - * @returns {boolean|boolean} - */ - shouldShowEmailError() { - return this.state.isEmailValidated && !this.isEmailValid(); - } - - /** - * The user has unfocused the input, his password is ready to be validated - */ - validatePassword = () => this.setState({isPasswordValidated: true}); - - /** - * Checks if the user has entered a password - * - * @returns {boolean} - */ - isPasswordValid() { - return this.state.password !== ''; - } - - /** - * Checks if we should tell the user his password is invalid. - * We should only show this if his password is invalid and has been checked when un-focusing the input - * - * @returns {boolean|boolean} - */ - shouldShowPasswordError() { - return this.state.isPasswordValidated && !this.isPasswordValid(); - } - - /** - * If the email and password are valid, and we are not loading a request, then the login button can be enabled - * - * @returns {boolean} - */ - shouldEnableLogin() { - return this.isEmailValid() && this.isPasswordValid() && !this.state.loading; - } - - /** - * Called when the user input changes in the email or password field. - * This saves the new value in the State and disabled input validation (to prevent errors to show while typing) - * - * @param isEmail True if the field is the email field - * @param value The new field value - */ - onInputChange(isEmail: boolean, value: string) { - if (isEmail) { - this.setState({ - email: value, - isEmailValidated: false, - }); - } else { - this.setState({ - password: value, - isPasswordValidated: false, - }); - } - } - - /** - * Focuses the password field when the email field is done - * - * @returns {*} - */ - onEmailSubmit = () => { - if (this.passwordInputRef.current != null) - this.passwordInputRef.current.focus(); - } - - /** - * Called when the user clicks on login or finishes to type his password. - * - * Checks if we should allow the user to login, - * then makes the login request and enters a loading state until the request finishes - * - */ - onSubmit = () => { - if (this.shouldEnableLogin()) { - this.setState({loading: true}); - ConnectionManager.getInstance().connect(this.state.email, this.state.password) - .then(this.handleSuccess) - .catch(this.showErrorDialog) - .finally(() => { - this.setState({loading: false}); - }); - } - }; - - /** - * Gets the form input - * - * @returns {*} - */ - getFormInput() { - return ( - - - - {i18n.t("screens.login.emailError")} - - - - {i18n.t("screens.login.passwordError")} - - - ); - } - - /** - * Gets the card containing the input form - * @returns {*} - */ - getMainCard() { - return ( - - } - /> - - {this.getFormInput()} - - - - - - - - - - - ); - } - - render() { - return ( - - - - - {this.getMainCard()} - - - - - - - ); - } -} - const styles = StyleSheet.create({ - container: { - flex: 1, - }, - card: { - marginTop: 'auto', - marginBottom: 'auto', - }, - header: { - fontSize: 36, - marginBottom: 48 - }, - textInput: {}, - btnContainer: { - marginTop: 5, - marginBottom: 10, - } + container: { + flex: 1, + }, + card: { + marginTop: 'auto', + marginBottom: 'auto', + }, + header: { + fontSize: 36, + marginBottom: 48, + }, + textInput: {}, + btnContainer: { + marginTop: 5, + marginBottom: 10, + }, }); +class LoginScreen extends React.Component { + onEmailChange: (value: string) => void; + + onPasswordChange: (value: string) => void; + + passwordInputRef: {current: null | TextInput}; + + nextScreen: string | null; + + constructor(props: PropsType) { + super(props); + this.passwordInputRef = React.createRef(); + this.onEmailChange = (value: string) => { + this.onInputChange(true, value); + }; + this.onPasswordChange = (value: string) => { + this.onInputChange(false, value); + }; + props.navigation.addListener('focus', this.onScreenFocus); + this.state = { + email: '', + password: '', + isEmailValidated: false, + isPasswordValidated: false, + loading: false, + dialogVisible: false, + dialogError: 0, + mascotDialogVisible: AsyncStorageManager.getBool( + AsyncStorageManager.PREFERENCES.loginShowBanner.key, + ), + }; + } + + onScreenFocus = () => { + this.handleNavigationParams(); + }; + + /** + * Navigates to the Amicale website screen with the reset password link as navigation parameters + */ + onResetPasswordClick = () => { + const {navigation} = this.props; + navigation.navigate('website', { + host: AvailableWebsites.websites.AMICALE, + path: RESET_PASSWORD_PATH, + title: i18n.t('screens.websites.amicale'), + }); + }; + + /** + * Called when the user input changes in the email or password field. + * This saves the new value in the State and disabled input validation (to prevent errors to show while typing) + * + * @param isEmail True if the field is the email field + * @param value The new field value + */ + onInputChange(isEmail: boolean, value: string) { + if (isEmail) { + this.setState({ + email: value, + isEmailValidated: false, + }); + } else { + this.setState({ + password: value, + isPasswordValidated: false, + }); + } + } + + /** + * Focuses the password field when the email field is done + * + * @returns {*} + */ + onEmailSubmit = () => { + if (this.passwordInputRef.current != null) + this.passwordInputRef.current.focus(); + }; + + /** + * Called when the user clicks on login or finishes to type his password. + * + * Checks if we should allow the user to login, + * then makes the login request and enters a loading state until the request finishes + * + */ + onSubmit = () => { + const {email, password} = this.state; + if (this.shouldEnableLogin()) { + this.setState({loading: true}); + ConnectionManager.getInstance() + .connect(email, password) + .then(this.handleSuccess) + .catch(this.showErrorDialog) + .finally(() => { + this.setState({loading: false}); + }); + } + }; + + /** + * Gets the form input + * + * @returns {*} + */ + getFormInput(): React.Node { + const {email, password} = this.state; + return ( + + + + {i18n.t('screens.login.emailError')} + + + + {i18n.t('screens.login.passwordError')} + + + ); + } + + /** + * Gets the card containing the input form + * @returns {*} + */ + getMainCard(): React.Node { + const {props, state} = this; + return ( + + ( + + )} + /> + + {this.getFormInput()} + + + + + + + + + + ); + } + + /** + * The user has unfocused the input, his email is ready to be validated + */ + validateEmail = () => { + this.setState({isEmailValidated: true}); + }; + + /** + * The user has unfocused the input, his password is ready to be validated + */ + validatePassword = () => { + this.setState({isPasswordValidated: true}); + }; + + hideMascotDialog = () => { + AsyncStorageManager.set( + AsyncStorageManager.PREFERENCES.loginShowBanner.key, + false, + ); + this.setState({mascotDialogVisible: false}); + }; + + showMascotDialog = () => { + this.setState({mascotDialogVisible: true}); + }; + + /** + * Shows an error dialog with the corresponding login error + * + * @param error The error given by the login request + */ + showErrorDialog = (error: number) => { + this.setState({ + dialogVisible: true, + dialogError: error, + }); + }; + + hideErrorDialog = () => { + this.setState({dialogVisible: false}); + }; + + /** + * Navigates to the screen specified in navigation parameters or simply go back tha stack. + * Saves in user preferences to not show the login banner again. + */ + handleSuccess = () => { + const {navigation} = this.props; + // Do not show the home login banner again + AsyncStorageManager.set( + AsyncStorageManager.PREFERENCES.homeShowBanner.key, + false, + ); + if (this.nextScreen == null) navigation.goBack(); + else navigation.replace(this.nextScreen); + }; + + /** + * Saves the screen to navigate to after a successful login if one was provided in navigation parameters + */ + handleNavigationParams() { + const {route} = this.props; + if (route.params != null) { + if (route.params.nextScreen != null) + this.nextScreen = route.params.nextScreen; + else this.nextScreen = null; + } + } + + /** + * Checks if the entered email is valid (matches the regex) + * + * @returns {boolean} + */ + isEmailValid(): boolean { + const {email} = this.state; + return emailRegex.test(email); + } + + /** + * Checks if we should tell the user his email is invalid. + * We should only show this if his email is invalid and has been checked when un-focusing the input + * + * @returns {boolean|boolean} + */ + shouldShowEmailError(): boolean { + const {isEmailValidated} = this.state; + return isEmailValidated && !this.isEmailValid(); + } + + /** + * Checks if the user has entered a password + * + * @returns {boolean} + */ + isPasswordValid(): boolean { + const {password} = this.state; + return password !== ''; + } + + /** + * Checks if we should tell the user his password is invalid. + * We should only show this if his password is invalid and has been checked when un-focusing the input + * + * @returns {boolean|boolean} + */ + shouldShowPasswordError(): boolean { + const {isPasswordValidated} = this.state; + return isPasswordValidated && !this.isPasswordValid(); + } + + /** + * If the email and password are valid, and we are not loading a request, then the login button can be enabled + * + * @returns {boolean} + */ + shouldEnableLogin(): boolean { + const {loading} = this.state; + return this.isEmailValid() && this.isPasswordValid() && !loading; + } + + render(): React.Node { + const {mascotDialogVisible, dialogVisible, dialogError} = this.state; + return ( + + + + {this.getMainCard()} + + + + + + ); + } +} + export default withTheme(LoginScreen); diff --git a/src/screens/Amicale/ProfileScreen.js b/src/screens/Amicale/ProfileScreen.js index d107e3a..6e64379 100644 --- a/src/screens/Amicale/ProfileScreen.js +++ b/src/screens/Amicale/ProfileScreen.js @@ -1,432 +1,467 @@ // @flow import * as React from 'react'; -import {FlatList, StyleSheet, View} from "react-native"; -import {Avatar, Button, Card, Divider, List, Paragraph, withTheme} from 'react-native-paper'; -import AuthenticatedScreen from "../../components/Amicale/AuthenticatedScreen"; +import {FlatList, StyleSheet, View} from 'react-native'; +import { + Avatar, + Button, + Card, + Divider, + List, + Paragraph, + withTheme, +} from 'react-native-paper'; import i18n from 'i18n-js'; -import LogoutDialog from "../../components/Amicale/LogoutDialog"; -import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton"; -import type {cardList} from "../../components/Lists/CardList/CardList"; -import CardList from "../../components/Lists/CardList/CardList"; -import {StackNavigationProp} from "@react-navigation/stack"; -import type {CustomTheme} from "../../managers/ThemeManager"; -import AvailableWebsites from "../../constants/AvailableWebsites"; -import Mascot, {MASCOT_STYLE} from "../../components/Mascot/Mascot"; -import ServicesManager, {SERVICES_KEY} from "../../managers/ServicesManager"; -import CollapsibleFlatList from "../../components/Collapsible/CollapsibleFlatList"; +import {StackNavigationProp} from '@react-navigation/stack'; +import AuthenticatedScreen from '../../components/Amicale/AuthenticatedScreen'; +import LogoutDialog from '../../components/Amicale/LogoutDialog'; +import MaterialHeaderButtons, { + Item, +} from '../../components/Overrides/CustomHeaderButton'; +import CardList from '../../components/Lists/CardList/CardList'; +import type {CustomThemeType} from '../../managers/ThemeManager'; +import AvailableWebsites from '../../constants/AvailableWebsites'; +import Mascot, {MASCOT_STYLE} from '../../components/Mascot/Mascot'; +import ServicesManager, {SERVICES_KEY} from '../../managers/ServicesManager'; +import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; +import type {ServiceItemType} from '../../managers/ServicesManager'; -type Props = { - navigation: StackNavigationProp, - theme: CustomTheme, -} +type PropsType = { + navigation: StackNavigationProp, + theme: CustomThemeType, +}; -type State = { - dialogVisible: boolean, -} +type StateType = { + dialogVisible: boolean, +}; -type ProfileData = { - first_name: string, - last_name: string, - email: string, - birthday: string, - phone: string, - branch: string, - link: string, - validity: boolean, - clubs: Array, -} -type Club = { - id: number, - name: string, - is_manager: boolean, -} +type ClubType = { + id: number, + name: string, + is_manager: boolean, +}; -class ProfileScreen extends React.Component { - - state = { - dialogVisible: false, - }; - - data: ProfileData; - - flatListData: Array<{ id: string }>; - amicaleDataset: cardList; - - constructor(props: Props) { - super(props); - this.flatListData = [ - {id: '0'}, - {id: '1'}, - {id: '2'}, - {id: '3'}, - ] - const services = new ServicesManager(props.navigation); - this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]); - } - - componentDidMount() { - this.props.navigation.setOptions({ - headerRight: this.getHeaderButton, - }); - } - - showDisconnectDialog = () => this.setState({dialogVisible: true}); - - hideDisconnectDialog = () => this.setState({dialogVisible: false}); - - /** - * Gets the logout header button - * - * @returns {*} - */ - getHeaderButton = () => - - ; - - /** - * Gets the main screen component with the fetched data - * - * @param data The data fetched from the server - * @returns {*} - */ - getScreen = (data: Array<{ [key: string]: any } | null>) => { - if (data[0] != null) { - this.data = data[0]; - } - return ( - - - - - ) - }; - - getRenderItem = ({item}: { item: { id: string } }) => { - switch (item.id) { - case '0': - return this.getWelcomeCard(); - case '1': - return this.getPersonalCard(); - case '2': - return this.getClubCard(); - default: - return this.getMembershipCar(); - } - }; - - /** - * Gets the list of services available with the Amicale account - * - * @returns {*} - */ - getServicesList() { - return ( - - ); - } - - /** - * Gets a card welcoming the user to his account - * - * @returns {*} - */ - getWelcomeCard() { - return ( - - - } - titleStyle={{marginLeft: 10}} - /> - - - - {i18n.t("screens.profile.welcomeDescription")} - - {this.getServicesList()} - - {i18n.t("screens.profile.welcomeFeedback")} - - - - - - - - ); - } - - /** - * Checks if the given field is available - * - * @param field The field to check - * @return {boolean} - */ - isFieldAvailable(field: ?string) { - return field !== null; - } - - /** - * Gets the given field value. - * If the field does not have a value, returns a placeholder text - * - * @param field The field to get the value from - * @return {*} - */ - getFieldValue(field: ?string) { - return this.isFieldAvailable(field) - ? field - : i18n.t("screens.profile.noData"); - } - - /** - * Gets a list item showing personal information - * - * @param field The field to display - * @param icon The icon to use - * @return {*} - */ - getPersonalListItem(field: ?string, icon: string) { - let title = this.isFieldAvailable(field) ? this.getFieldValue(field) : ':('; - let subtitle = this.isFieldAvailable(field) ? '' : this.getFieldValue(field); - return ( - } - /> - ); - } - - /** - * Gets a card containing user personal information - * - * @return {*} - */ - getPersonalCard() { - return ( - - } - /> - - - - {i18n.t("screens.profile.personalInformation")} - {this.getPersonalListItem(this.data.birthday, "cake-variant")} - {this.getPersonalListItem(this.data.phone, "phone")} - {this.getPersonalListItem(this.data.email, "email")} - {this.getPersonalListItem(this.data.branch, "school")} - - - - - - - - ); - } - - /** - * Gets a cars containing clubs the user is part of - * - * @return {*} - */ - getClubCard() { - return ( - - } - /> - - - {this.getClubList(this.data.clubs)} - - - ); - } - - /** - * Gets a card showing if the user has payed his membership - * - * @return {*} - */ - getMembershipCar() { - return ( - - } - /> - - - {this.getMembershipItem(this.data.validity)} - - - - ); - } - - /** - * Gets the item showing if the user has payed his membership - * - * @return {*} - */ - getMembershipItem(state: boolean) { - return ( - } - /> - ); - } - - /** - * Opens the club details screen for the club of given ID - * @param id The club's id to open - */ - openClubDetailsScreen(id: number) { - this.props.navigation.navigate("club-information", {clubId: id}); - } - - /** - * Gets a list item for the club list - * - * @param item The club to render - * @return {*} - */ - clubListItem = ({item}: { item: Club }) => { - const onPress = () => this.openClubDetailsScreen(item.id); - let description = i18n.t("screens.profile.isMember"); - let icon = (props) => ; - if (item.is_manager) { - description = i18n.t("screens.profile.isManager"); - icon = (props) => ; - } - return ; - }; - - clubKeyExtractor = (item: Club) => item.name; - - sortClubList = (a: Club, b: Club) => a.is_manager ? -1 : 1; - - /** - * Renders the list of clubs the user is part of - * - * @param list The club list - * @return {*} - */ - getClubList(list: Array) { - list.sort(this.sortClubList); - return ( - //$FlowFixMe - - ); - } - - render() { - return ( - - ); - } -} +type ProfileDataType = { + first_name: string, + last_name: string, + email: string, + birthday: string, + phone: string, + branch: string, + link: string, + validity: boolean, + clubs: Array, +}; const styles = StyleSheet.create({ - card: { - margin: 10, - }, - icon: { - backgroundColor: 'transparent' - }, - editButton: { - marginLeft: 'auto' - } - + card: { + margin: 10, + }, + icon: { + backgroundColor: 'transparent', + }, + editButton: { + marginLeft: 'auto', + }, }); +class ProfileScreen extends React.Component { + data: ProfileDataType; + + flatListData: Array<{id: string}>; + + amicaleDataset: Array; + + constructor(props: PropsType) { + super(props); + this.flatListData = [{id: '0'}, {id: '1'}, {id: '2'}, {id: '3'}]; + const services = new ServicesManager(props.navigation); + this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]); + this.state = { + dialogVisible: false, + }; + } + + componentDidMount() { + const {navigation} = this.props; + navigation.setOptions({ + headerRight: this.getHeaderButton, + }); + } + + /** + * Gets the logout header button + * + * @returns {*} + */ + getHeaderButton = (): React.Node => ( + + + + ); + + /** + * Gets the main screen component with the fetched data + * + * @param data The data fetched from the server + * @returns {*} + */ + getScreen = (data: Array): React.Node => { + const {dialogVisible} = this.state; + const {navigation} = this.props; + // eslint-disable-next-line prefer-destructuring + if (data[0] != null) this.data = data[0]; + + return ( + + + + + ); + }; + + getRenderItem = ({item}: {item: {id: string}}): React.Node => { + switch (item.id) { + case '0': + return this.getWelcomeCard(); + case '1': + return this.getPersonalCard(); + case '2': + return this.getClubCard(); + default: + return this.getMembershipCar(); + } + }; + + /** + * Gets the list of services available with the Amicale account + * + * @returns {*} + */ + getServicesList(): React.Node { + return ; + } + + /** + * Gets a card welcoming the user to his account + * + * @returns {*} + */ + getWelcomeCard(): React.Node { + const {navigation} = this.props; + return ( + + ( + + )} + titleStyle={{marginLeft: 10}} + /> + + + {i18n.t('screens.profile.welcomeDescription')} + {this.getServicesList()} + {i18n.t('screens.profile.welcomeFeedback')} + + + + + + + ); + } + + /** + * Gets the given field value. + * If the field does not have a value, returns a placeholder text + * + * @param field The field to get the value from + * @return {*} + */ + static getFieldValue(field: ?string): string { + return field != null ? field : i18n.t('screens.profile.noData'); + } + + /** + * Gets a list item showing personal information + * + * @param field The field to display + * @param icon The icon to use + * @return {*} + */ + getPersonalListItem(field: ?string, icon: string): React.Node { + const {theme} = this.props; + const title = field != null ? ProfileScreen.getFieldValue(field) : ':('; + const subtitle = field != null ? '' : ProfileScreen.getFieldValue(field); + return ( + ( + + )} + /> + ); + } + + /** + * Gets a card containing user personal information + * + * @return {*} + */ + getPersonalCard(): React.Node { + const {theme, navigation} = this.props; + return ( + + ( + + )} + /> + + + + + {i18n.t('screens.profile.personalInformation')} + + {this.getPersonalListItem(this.data.birthday, 'cake-variant')} + {this.getPersonalListItem(this.data.phone, 'phone')} + {this.getPersonalListItem(this.data.email, 'email')} + {this.getPersonalListItem(this.data.branch, 'school')} + + + + + + + + ); + } + + /** + * Gets a cars containing clubs the user is part of + * + * @return {*} + */ + getClubCard(): React.Node { + const {theme} = this.props; + return ( + + ( + + )} + /> + + + {this.getClubList(this.data.clubs)} + + + ); + } + + /** + * Gets a card showing if the user has payed his membership + * + * @return {*} + */ + getMembershipCar(): React.Node { + const {theme} = this.props; + return ( + + ( + + )} + /> + + + {this.getMembershipItem(this.data.validity)} + + + + ); + } + + /** + * Gets the item showing if the user has payed his membership + * + * @return {*} + */ + getMembershipItem(state: boolean): React.Node { + const {theme} = this.props; + return ( + ( + + )} + /> + ); + } + + /** + * Gets a list item for the club list + * + * @param item The club to render + * @return {*} + */ + getClubListItem = ({item}: {item: ClubType}): React.Node => { + const {theme} = this.props; + const onPress = () => { + this.openClubDetailsScreen(item.id); + }; + let description = i18n.t('screens.profile.isMember'); + let icon = ({size, color}: {size: number, color: string}): React.Node => ( + + ); + if (item.is_manager) { + description = i18n.t('screens.profile.isManager'); + icon = ({size}: {size: number}): React.Node => ( + + ); + } + return ( + + ); + }; + + /** + * Renders the list of clubs the user is part of + * + * @param list The club list + * @return {*} + */ + getClubList(list: Array): React.Node { + list.sort(this.sortClubList); + return ( + + ); + } + + clubKeyExtractor = (item: ClubType): string => item.name; + + sortClubList = (a: ClubType): number => (a.is_manager ? -1 : 1); + + showDisconnectDialog = () => { + this.setState({dialogVisible: true}); + }; + + hideDisconnectDialog = () => { + this.setState({dialogVisible: false}); + }; + + /** + * Opens the club details screen for the club of given ID + * @param id The club's id to open + */ + openClubDetailsScreen(id: number) { + const {navigation} = this.props; + navigation.navigate('club-information', {clubId: id}); + } + + render(): React.Node { + const {navigation} = this.props; + return ( + + ); + } +} + export default withTheme(ProfileScreen);