123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /*
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
- *
- * This file is part of Campus INSAT.
- *
- * Campus INSAT is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Campus INSAT is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
- */
-
- import * as React from 'react';
- import { StackNavigationProp } from '@react-navigation/stack';
- import ConnectionManager from '../../managers/ConnectionManager';
- import { ERROR_TYPE } from '../../utils/WebData';
- import ErrorView from '../Screens/ErrorView';
- import BasicLoadingScreen from '../Screens/BasicLoadingScreen';
- import i18n from 'i18n-js';
-
- type PropsType<T> = {
- navigation: StackNavigationProp<any>;
- requests: Array<{
- link: string;
- params: object;
- mandatory: boolean;
- }>;
- renderFunction: (data: Array<T | null>) => React.ReactNode;
- errorViewOverride?: Array<{
- errorCode: number;
- message: string;
- icon: string;
- showRetryButton: boolean;
- }> | null;
- };
-
- type StateType = {
- loading: boolean;
- };
-
- class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
- static defaultProps = {
- errorViewOverride: null,
- };
-
- currentUserToken: string | null;
-
- connectionManager: ConnectionManager;
-
- errors: Array<number>;
-
- fetchedData: Array<T | null>;
-
- constructor(props: PropsType<T>) {
- super(props);
- this.state = {
- loading: true,
- };
- this.currentUserToken = null;
- this.connectionManager = ConnectionManager.getInstance();
- props.navigation.addListener('focus', this.onScreenFocus);
- this.fetchedData = new Array(props.requests.length);
- this.errors = new Array(props.requests.length);
- }
-
- /**
- * Refreshes screen if user changed
- */
- onScreenFocus = () => {
- if (this.currentUserToken !== this.connectionManager.getToken()) {
- this.currentUserToken = this.connectionManager.getToken();
- this.fetchData();
- }
- };
-
- /**
- * Callback used when a request finishes, successfully or not.
- * Saves data and error code.
- * If the token is invalid, logout the user and open the login screen.
- * If the last request was received, stop the loading screen.
- *
- * @param data The data fetched from the server
- * @param index The index for the data
- * @param error The error code received
- */
- onRequestFinished(data: T | null, index: number, error?: number) {
- const { props } = this;
- if (index >= 0 && index < props.requests.length) {
- this.fetchedData[index] = data;
- this.errors[index] = error != null ? error : ERROR_TYPE.SUCCESS;
- }
- // Token expired, logout user
- if (error === ERROR_TYPE.BAD_TOKEN) {
- this.connectionManager.disconnect();
- }
-
- if (this.allRequestsFinished()) {
- this.setState({ loading: false });
- }
- }
-
- /**
- * Gets the error to render.
- * Non-mandatory requests are ignored.
- *
- *
- * @return {number} The error code or ERROR_TYPE.SUCCESS if no error was found
- */
- getError(): number {
- const { props } = this;
- for (let i = 0; i < this.errors.length; i += 1) {
- if (
- this.errors[i] !== ERROR_TYPE.SUCCESS &&
- props.requests[i].mandatory
- ) {
- return this.errors[i];
- }
- }
- return ERROR_TYPE.SUCCESS;
- }
-
- /**
- * Gets the error view to display in case of error
- *
- * @return {*}
- */
- getErrorRender() {
- const { props } = this;
- const errorCode = this.getError();
- let shouldOverride = false;
- let override = null;
- const overrideList = props.errorViewOverride;
- if (overrideList != null) {
- for (let i = 0; i < overrideList.length; i += 1) {
- if (overrideList[i].errorCode === errorCode) {
- shouldOverride = true;
- override = overrideList[i];
- break;
- }
- }
- }
-
- if (shouldOverride && override != null) {
- return (
- <ErrorView
- icon={override.icon}
- message={override.message}
- button={
- override.showRetryButton
- ? {
- icon: 'refresh',
- text: i18n.t('general.retry'),
- onPress: this.fetchData,
- }
- : undefined
- }
- />
- );
- }
- return (
- <ErrorView
- status={errorCode}
- button={{
- icon: 'refresh',
- text: i18n.t('general.retry'),
- onPress: this.fetchData,
- }}
- />
- );
- }
-
- /**
- * Fetches the data from the server.
- *
- * If the user is not logged in errorCode is set to BAD_TOKEN and all requests fail.
- *
- * If the user is logged in, send all requests.
- */
- fetchData = () => {
- const { state, props } = this;
- if (!state.loading) {
- this.setState({ loading: true });
- }
-
- if (this.connectionManager.isLoggedIn()) {
- for (let i = 0; i < props.requests.length; i += 1) {
- this.connectionManager
- .authenticatedRequest<T>(
- props.requests[i].link,
- props.requests[i].params
- )
- .then((response: T): void => this.onRequestFinished(response, i))
- .catch((error: number): void =>
- this.onRequestFinished(null, i, error)
- );
- }
- } else {
- for (let i = 0; i < props.requests.length; i += 1) {
- this.onRequestFinished(null, i, ERROR_TYPE.BAD_TOKEN);
- }
- }
- };
-
- /**
- * Checks if all requests finished processing
- *
- * @return {boolean} True if all finished
- */
- allRequestsFinished(): boolean {
- let finished = true;
- this.errors.forEach((error: number | null) => {
- if (error == null) {
- finished = false;
- }
- });
- return finished;
- }
-
- /**
- * Reloads the data, to be called using ref by parent components
- */
- reload() {
- this.fetchData();
- }
-
- render() {
- const { state, props } = this;
- if (state.loading) {
- return <BasicLoadingScreen />;
- }
- if (this.getError() === ERROR_TYPE.SUCCESS) {
- return props.renderFunction(this.fetchedData);
- }
- return this.getErrorRender();
- }
- }
-
- export default AuthenticatedScreen;
|