123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- /*
- * 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 WebView from 'react-native-webview';
- import {
- Divider,
- HiddenItem,
- OverflowMenu,
- } from 'react-navigation-header-buttons';
- import i18n from 'i18n-js';
- import {
- Animated,
- BackHandler,
- Linking,
- NativeScrollEvent,
- NativeSyntheticEvent,
- } 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 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 PropsType = {
- navigation: StackNavigationProp<any>;
- theme: ReactNativePaper.Theme;
- url: string;
- collapsibleStack: Collapsible;
- onMessage: (event: {nativeEvent: {data: string}}) => void;
- onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => 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<PropsType> {
- static defaultProps = {
- customJS: '',
- showAdvancedControls: true,
- customPaddingFunction: null,
- };
-
- currentUrl: string;
-
- webviewRef: {current: null | WebView};
-
- canGoBack: boolean;
-
- constructor(props: PropsType) {
- super(props);
- this.webviewRef = React.createRef();
- this.canGoBack = false;
- this.currentUrl = props.url;
- }
-
- /**
- * 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;
- };
-
- /**
- * Gets header refresh and open in browser buttons
- *
- * @return {*}
- */
- getBasicButton = () => {
- return (
- <MaterialHeaderButtons>
- <Item
- title="refresh"
- iconName="refresh"
- onPress={this.onRefreshClicked}
- />
- <Item
- title={i18n.t('general.openInBrowser')}
- iconName="open-in-new"
- onPress={this.onOpenClicked}
- />
- </MaterialHeaderButtons>
- );
- };
-
- /**
- * Creates advanced header control buttons.
- * These buttons allows the user to refresh, go back, go forward and open in the browser.
- *
- * @returns {*}
- */
- getAdvancedButtons = () => {
- const {props} = this;
- return (
- <MaterialHeaderButtons>
- <Item
- title="refresh"
- iconName="refresh"
- onPress={this.onRefreshClicked}
- />
- <OverflowMenu
- style={{marginHorizontal: 10}}
- OverflowIcon={
- <MaterialCommunityIcons
- name="dots-vertical"
- size={26}
- color={props.theme.colors.text}
- />
- }>
- <HiddenItem
- title={i18n.t('general.goBack')}
- onPress={this.onGoBackClicked}
- />
- <HiddenItem
- title={i18n.t('general.goForward')}
- onPress={this.onGoForwardClicked}
- />
- <Divider />
- <HiddenItem
- title={i18n.t('general.openInBrowser')}
- onPress={this.onOpenClicked}
- />
- </OverflowMenu>
- </MaterialHeaderButtons>
- );
- };
-
- /**
- * Gets the loading indicator
- *
- * @return {*}
- */
- getRenderLoading = () => <BasicLoadingScreen isAbsolute />;
-
- /**
- * 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 = () => {
- Linking.openURL(this.currentUrl);
- };
-
- onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
- 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() {
- const {props} = this;
- const {containerPaddingTop, onScrollWithListener} = props.collapsibleStack;
- return (
- <AnimatedWebView
- ref={this.webviewRef}
- source={{uri: props.url}}
- startInLoadingState
- injectedJavaScript={props.customJS}
- javaScriptEnabled
- renderLoading={this.getRenderLoading}
- renderError={() => (
- <ErrorView
- errorCode={ERROR_TYPE.CONNECTION_ERROR}
- onRefresh={this.onRefreshClicked}
- />
- )}
- onNavigationStateChange={(navState) => {
- this.currentUrl = navState.url;
- this.canGoBack = navState.canGoBack;
- }}
- onMessage={props.onMessage}
- onLoad={() => {
- this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop));
- }}
- // Animations
- onScroll={(event) => onScrollWithListener(this.onScroll)(event)}
- />
- );
- }
- }
-
- export default withCollapsible(withTheme(WebViewScreen));
|