diff --git a/src/components/Screens/BasicLoadingScreen.js b/src/components/Screens/BasicLoadingScreen.js index 01ad378..2b21eb3 100644 --- a/src/components/Screens/BasicLoadingScreen.js +++ b/src/components/Screens/BasicLoadingScreen.js @@ -3,6 +3,7 @@ import * as React from 'react'; import {View} from 'react-native'; import {ActivityIndicator, withTheme} from 'react-native-paper'; +import type {CustomTheme} from '../../managers/ThemeManager'; /** * Component used to display a header button @@ -10,28 +11,29 @@ import {ActivityIndicator, withTheme} from 'react-native-paper'; * @param props Props to pass to the component * @return {*} */ -function BasicLoadingScreen(props) { - const {colors} = props.theme; - let position = undefined; - if (props.isAbsolute !== undefined && props.isAbsolute) - position = 'absolute'; +function BasicLoadingScreen(props: { + theme: CustomTheme, + isAbsolute: boolean, +}): React.Node { + const {theme, isAbsolute} = props; + const {colors} = theme; + let position; + if (isAbsolute != null && isAbsolute) position = 'absolute'; - return ( - - - - ); + return ( + + + + ); } export default withTheme(BasicLoadingScreen); diff --git a/src/components/Screens/ErrorView.js b/src/components/Screens/ErrorView.js index 141181c..3383c7a 100644 --- a/src/components/Screens/ErrorView.js +++ b/src/components/Screens/ErrorView.js @@ -2,191 +2,189 @@ import * as React from 'react'; import {Button, Subheading, withTheme} from 'react-native-paper'; -import {StyleSheet, View} from "react-native"; -import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons"; +import {StyleSheet, View} from 'react-native'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import i18n from 'i18n-js'; -import {ERROR_TYPE} from "../../utils/WebData"; import * as Animatable from 'react-native-animatable'; +import {StackNavigationProp} from '@react-navigation/stack'; +import {ERROR_TYPE} from '../../utils/WebData'; +import type {CustomTheme} from '../../managers/ThemeManager'; -type Props = { - navigation: Object, - route: Object, - errorCode: number, - onRefresh: Function, - icon: string, - message: string, - showRetryButton: boolean, -} - -type State = { - refreshing: boolean, -} - -class ErrorView extends React.PureComponent { - - colors: Object; - - message: string; - icon: string; - - showLoginButton: boolean; - - static defaultProps = { - errorCode: 0, - icon: '', - message: '', - showRetryButton: true, - } - - state = { - refreshing: false, - }; - - constructor(props) { - super(props); - this.colors = props.theme.colors; - this.icon = ""; - } - - generateMessage() { - this.showLoginButton = false; - if (this.props.errorCode !== 0) { - switch (this.props.errorCode) { - case ERROR_TYPE.BAD_CREDENTIALS: - this.message = i18n.t("errors.badCredentials"); - this.icon = "account-alert-outline"; - break; - case ERROR_TYPE.BAD_TOKEN: - this.message = i18n.t("errors.badToken"); - this.icon = "account-alert-outline"; - this.showLoginButton = true; - break; - case ERROR_TYPE.NO_CONSENT: - this.message = i18n.t("errors.noConsent"); - this.icon = "account-remove-outline"; - break; - case ERROR_TYPE.TOKEN_SAVE: - this.message = i18n.t("errors.tokenSave"); - this.icon = "alert-circle-outline"; - break; - case ERROR_TYPE.BAD_INPUT: - this.message = i18n.t("errors.badInput"); - this.icon = "alert-circle-outline"; - break; - case ERROR_TYPE.FORBIDDEN: - this.message = i18n.t("errors.forbidden"); - this.icon = "lock"; - break; - case ERROR_TYPE.CONNECTION_ERROR: - this.message = i18n.t("errors.connectionError"); - this.icon = "access-point-network-off"; - break; - case ERROR_TYPE.SERVER_ERROR: - this.message = i18n.t("errors.serverError"); - this.icon = "server-network-off"; - break; - default: - this.message = i18n.t("errors.unknown"); - this.icon = "alert-circle-outline"; - break; - } - this.message += "\n\nCode " + this.props.errorCode; - } else { - this.message = this.props.message; - this.icon = this.props.icon; - } - - } - - getRetryButton() { - return ; - } - - goToLogin = () => { - this.props.navigation.navigate("login", - { - screen: 'login', - params: {nextScreen: this.props.route.name} - }) - }; - - getLoginButton() { - return ; - } - - render() { - this.generateMessage(); - return ( - - - - - - - {this.message} - - {this.props.showRetryButton - ? (this.showLoginButton - ? this.getLoginButton() - : this.getRetryButton()) - : null} - - - ); - } -} +type PropsType = { + navigation: StackNavigationProp, + theme: CustomTheme, + route: {name: string}, + onRefresh?: () => void, + errorCode?: number, + icon?: string, + message?: string, + showRetryButton?: boolean, +}; const styles = StyleSheet.create({ - outer: { - height: '100%', - }, - inner: { - marginTop: 'auto', - marginBottom: 'auto', - }, - iconContainer: { - marginLeft: 'auto', - marginRight: 'auto', - marginBottom: 20 - }, - subheading: { - textAlign: 'center', - paddingHorizontal: 20 - }, - button: { - marginTop: 10, - marginLeft: 'auto', - marginRight: 'auto', - } + outer: { + height: '100%', + }, + inner: { + marginTop: 'auto', + marginBottom: 'auto', + }, + iconContainer: { + marginLeft: 'auto', + marginRight: 'auto', + marginBottom: 20, + }, + subheading: { + textAlign: 'center', + paddingHorizontal: 20, + }, + button: { + marginTop: 10, + marginLeft: 'auto', + marginRight: 'auto', + }, }); +class ErrorView extends React.PureComponent { + static defaultProps = { + onRefresh: (): void => null, + errorCode: 0, + icon: '', + message: '', + showRetryButton: true, + }; + + message: string; + + icon: string; + + showLoginButton: boolean; + + constructor(props: PropsType) { + super(props); + this.icon = ''; + } + + getRetryButton(): React.Node { + const {props} = this; + return ( + + ); + } + + getLoginButton(): React.Node { + return ( + + ); + } + + goToLogin = () => { + const {props} = this; + props.navigation.navigate('login', { + screen: 'login', + params: {nextScreen: props.route.name}, + }); + }; + + generateMessage() { + const {props} = this; + this.showLoginButton = false; + if (props.errorCode !== 0) { + switch (props.errorCode) { + case ERROR_TYPE.BAD_CREDENTIALS: + this.message = i18n.t('errors.badCredentials'); + this.icon = 'account-alert-outline'; + break; + case ERROR_TYPE.BAD_TOKEN: + this.message = i18n.t('errors.badToken'); + this.icon = 'account-alert-outline'; + this.showLoginButton = true; + break; + case ERROR_TYPE.NO_CONSENT: + this.message = i18n.t('errors.noConsent'); + this.icon = 'account-remove-outline'; + break; + case ERROR_TYPE.TOKEN_SAVE: + this.message = i18n.t('errors.tokenSave'); + this.icon = 'alert-circle-outline'; + break; + case ERROR_TYPE.BAD_INPUT: + this.message = i18n.t('errors.badInput'); + this.icon = 'alert-circle-outline'; + break; + case ERROR_TYPE.FORBIDDEN: + this.message = i18n.t('errors.forbidden'); + this.icon = 'lock'; + break; + case ERROR_TYPE.CONNECTION_ERROR: + this.message = i18n.t('errors.connectionError'); + this.icon = 'access-point-network-off'; + break; + case ERROR_TYPE.SERVER_ERROR: + this.message = i18n.t('errors.serverError'); + this.icon = 'server-network-off'; + break; + default: + this.message = i18n.t('errors.unknown'); + this.icon = 'alert-circle-outline'; + break; + } + this.message += `\n\nCode ${props.errorCode}`; + } else { + this.message = props.message; + this.icon = props.icon; + } + } + + render(): React.Node { + const {props} = this; + this.generateMessage(); + let button; + if (this.showLoginButton) button = this.getLoginButton(); + else if (props.showRetryButton) button = this.getRetryButton(); + else button = null; + + return ( + + + + + + + {this.message} + + {button} + + + ); + } +} export default withTheme(ErrorView); diff --git a/src/components/Screens/WebViewScreen.js b/src/components/Screens/WebViewScreen.js index 8dfb5f8..9dc715a 100644 --- a/src/components/Screens/WebViewScreen.js +++ b/src/components/Screens/WebViewScreen.js @@ -1,233 +1,247 @@ // @flow import * as React from 'react'; -import WebView from "react-native-webview"; -import BasicLoadingScreen from "./BasicLoadingScreen"; -import ErrorView from "./ErrorView"; -import {ERROR_TYPE} from "../../utils/WebData"; -import MaterialHeaderButtons, {Item} from '../Overrides/CustomHeaderButton'; -import {Divider, HiddenItem, OverflowMenu} from "react-navigation-header-buttons"; +import WebView from 'react-native-webview'; +import { + Divider, + HiddenItem, + OverflowMenu, +} from 'react-navigation-header-buttons'; import i18n from 'i18n-js'; -import {Animated, BackHandler, Linking} from "react-native"; -import {withCollapsible} from "../../utils/withCollapsible"; -import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons"; -import {withTheme} from "react-native-paper"; -import type {CustomTheme} from "../../managers/ThemeManager"; -import {StackNavigationProp} from "@react-navigation/stack"; -import {Collapsible} from "react-navigation-collapsible"; +import {Animated, BackHandler, Linking} from 'react-native'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import {withTheme} from 'react-native-paper'; +import {StackNavigationProp} from '@react-navigation/stack'; +import {Collapsible} from 'react-navigation-collapsible'; +import type {CustomTheme} from '../../managers/ThemeManager'; +import {withCollapsible} from '../../utils/withCollapsible'; +import MaterialHeaderButtons, {Item} from '../Overrides/CustomHeaderButton'; +import {ERROR_TYPE} from '../../utils/WebData'; +import ErrorView from './ErrorView'; +import BasicLoadingScreen from './BasicLoadingScreen'; -type Props = { - navigation: StackNavigationProp, - theme: CustomTheme, - url: string, - customJS: string, - customPaddingFunction: null | (padding: number) => string, - collapsibleStack: Collapsible, - onMessage: Function, - onScroll: Function, - showAdvancedControls: boolean, -} +type PropsType = { + navigation: StackNavigationProp, + theme: CustomTheme, + url: string, + collapsibleStack: Collapsible, + onMessage: (event: {nativeEvent: {data: string}}) => void, + onScroll: (event: SyntheticEvent) => void, + customJS?: string, + customPaddingFunction?: null | ((padding: number) => string), + showAdvancedControls?: boolean, +}; const AnimatedWebView = Animated.createAnimatedComponent(WebView); /** * Class defining a webview screen. */ -class WebViewScreen extends React.PureComponent { +class WebViewScreen extends React.PureComponent { + static defaultProps = { + customJS: '', + showAdvancedControls: true, + customPaddingFunction: null, + }; - static defaultProps = { - customJS: '', - showAdvancedControls: true, - customPaddingFunction: null, - }; + webviewRef: {current: null | WebView}; - webviewRef: { current: null | WebView }; + canGoBack: boolean; - canGoBack: boolean; + constructor() { + super(); + this.webviewRef = React.createRef(); + this.canGoBack = false; + } - constructor() { - super(); - this.webviewRef = React.createRef(); - this.canGoBack = false; + /** + * Creates header buttons and listens to events after mounting + */ + componentDidMount() { + const {props} = this; + props.navigation.setOptions({ + headerRight: props.showAdvancedControls + ? this.getAdvancedButtons + : this.getBasicButton, + }); + props.navigation.addListener('focus', () => { + BackHandler.addEventListener( + 'hardwareBackPress', + this.onBackButtonPressAndroid, + ); + }); + props.navigation.addListener('blur', () => { + BackHandler.removeEventListener( + 'hardwareBackPress', + this.onBackButtonPressAndroid, + ); + }); + } + + /** + * Goes back on the webview or on the navigation stack if we cannot go back anymore + * + * @returns {boolean} + */ + onBackButtonPressAndroid = (): boolean => { + if (this.canGoBack) { + this.onGoBackClicked(); + return true; } + return false; + }; - /** - * Creates header buttons and listens to events after mounting - */ - componentDidMount() { - this.props.navigation.setOptions({ - headerRight: this.props.showAdvancedControls - ? this.getAdvancedButtons - : this.getBasicButton, - }); - this.props.navigation.addListener( - 'focus', - () => - BackHandler.addEventListener( - 'hardwareBackPress', - this.onBackButtonPressAndroid - ) - ); - this.props.navigation.addListener( - 'blur', - () => - BackHandler.removeEventListener( - 'hardwareBackPress', - this.onBackButtonPressAndroid - ) - ); - } + /** + * Gets header refresh and open in browser buttons + * + * @return {*} + */ + getBasicButton = (): React.Node => { + return ( + + + + + ); + }; - /** - * Goes back on the webview or on the navigation stack if we cannot go back anymore - * - * @returns {boolean} - */ - onBackButtonPressAndroid = () => { - if (this.canGoBack) { - this.onGoBackClicked(); - return true; - } - return false; - }; - - /** - * Gets header refresh and open in browser buttons - * - * @return {*} - */ - getBasicButton = () => { - return ( - - - - - ); - }; - - /** - * Creates advanced header control buttons. - * These buttons allows the user to refresh, go back, go forward and open in the browser. - * - * @returns {*} - */ - getAdvancedButtons = () => { - return ( - - - } - > - - - - - - - ); - } - - /** - * Callback to use when refresh button is clicked. Reloads the webview. - */ - onRefreshClicked = () => { - if (this.webviewRef.current != null) - this.webviewRef.current.reload(); - } - onGoBackClicked = () => { - if (this.webviewRef.current != null) - this.webviewRef.current.goBack(); - } - onGoForwardClicked = () => { - if (this.webviewRef.current != null) - this.webviewRef.current.goForward(); - } - onOpenClicked = () => Linking.openURL(this.props.url); - - /** - * Injects the given javascript string into the web page - * - * @param script The script to inject - */ - injectJavaScript = (script: string) => { - if (this.webviewRef.current != null) - this.webviewRef.current.injectJavaScript(script); - } - - /** - * Gets the loading indicator - * - * @return {*} - */ - getRenderLoading = () => ; - - /** - * Gets the javascript needed to generate a padding on top of the page - * This adds padding to the body and runs the custom padding function given in props - * - * @param padding The padding to add in pixels - * @returns {string} - */ - getJavascriptPadding(padding: number) { - const customPadding = this.props.customPaddingFunction != null ? this.props.customPaddingFunction(padding) : ""; - return ( - "document.getElementsByTagName('body')[0].style.paddingTop = '" + padding + "px';" + - customPadding + - "true;" - ); - } - - onScroll = (event: Object) => { - if (this.props.onScroll) - this.props.onScroll(event); - } - - render() { - const {containerPaddingTop, onScrollWithListener} = this.props.collapsibleStack; - return ( - } - onNavigationStateChange={navState => { - this.canGoBack = navState.canGoBack; - }} - onMessage={this.props.onMessage} - onLoad={() => this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop))} - // Animations - onScroll={onScrollWithListener(this.onScroll)} + /** + * Creates advanced header control buttons. + * These buttons allows the user to refresh, go back, go forward and open in the browser. + * + * @returns {*} + */ + getAdvancedButtons = (): React.Node => { + const {props} = this; + return ( + + + - ); - } + }> + + + + + + + ); + }; + + /** + * Gets the loading indicator + * + * @return {*} + */ + getRenderLoading = (): React.Node => ; + + /** + * Gets the javascript needed to generate a padding on top of the page + * This adds padding to the body and runs the custom padding function given in props + * + * @param padding The padding to add in pixels + * @returns {string} + */ + getJavascriptPadding(padding: number): string { + const {props} = this; + const customPadding = + props.customPaddingFunction != null + ? props.customPaddingFunction(padding) + : ''; + return `document.getElementsByTagName('body')[0].style.paddingTop = '${padding}px';${customPadding}true;`; + } + + /** + * Callback to use when refresh button is clicked. Reloads the webview. + */ + onRefreshClicked = () => { + if (this.webviewRef.current != null) this.webviewRef.current.reload(); + }; + + onGoBackClicked = () => { + if (this.webviewRef.current != null) this.webviewRef.current.goBack(); + }; + + onGoForwardClicked = () => { + if (this.webviewRef.current != null) this.webviewRef.current.goForward(); + }; + + onOpenClicked = () => { + const {url} = this.props; + Linking.openURL(url); + }; + + onScroll = (event: SyntheticEvent) => { + const {onScroll} = this.props; + if (onScroll) onScroll(event); + }; + + /** + * Injects the given javascript string into the web page + * + * @param script The script to inject + */ + injectJavaScript = (script: string) => { + if (this.webviewRef.current != null) + this.webviewRef.current.injectJavaScript(script); + }; + + render(): React.Node { + const {props} = this; + const {containerPaddingTop, onScrollWithListener} = props.collapsibleStack; + return ( + ( + + )} + onNavigationStateChange={(navState: {canGoBack: boolean}) => { + this.canGoBack = navState.canGoBack; + }} + onMessage={props.onMessage} + onLoad={() => { + this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop)); + }} + // Animations + onScroll={onScrollWithListener(this.onScroll)} + /> + ); + } } export default withCollapsible(withTheme(WebViewScreen));