Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WebViewScreen.tsx 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import React, {
  20. useCallback,
  21. useEffect,
  22. useLayoutEffect,
  23. useRef,
  24. useState,
  25. } from 'react';
  26. import WebView from 'react-native-webview';
  27. import {
  28. Divider,
  29. HiddenItem,
  30. OverflowMenu,
  31. } from 'react-navigation-header-buttons';
  32. import i18n from 'i18n-js';
  33. import {
  34. Animated,
  35. BackHandler,
  36. Linking,
  37. NativeScrollEvent,
  38. NativeSyntheticEvent,
  39. StyleSheet,
  40. } from 'react-native';
  41. import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
  42. import { useTheme } from 'react-native-paper';
  43. import { useCollapsibleHeader } from 'react-navigation-collapsible';
  44. import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton';
  45. import { ERROR_TYPE } from '../../utils/WebData';
  46. import ErrorView from './ErrorView';
  47. import BasicLoadingScreen from './BasicLoadingScreen';
  48. import { useFocusEffect, useNavigation } from '@react-navigation/core';
  49. import { useCollapsible } from '../../utils/CollapsibleContext';
  50. type Props = {
  51. url: string;
  52. onMessage?: (event: { nativeEvent: { data: string } }) => void;
  53. onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
  54. initialJS?: string;
  55. injectJS?: string;
  56. customPaddingFunction?: null | ((padding: number) => string);
  57. showAdvancedControls?: boolean;
  58. };
  59. const AnimatedWebView = Animated.createAnimatedComponent(WebView);
  60. const styles = StyleSheet.create({
  61. overflow: {
  62. marginHorizontal: 10,
  63. },
  64. });
  65. /**
  66. * Class defining a webview screen.
  67. */
  68. function WebViewScreen(props: Props) {
  69. const [currentUrl, setCurrentUrl] = useState(props.url);
  70. const [canGoBack, setCanGoBack] = useState(false);
  71. const navigation = useNavigation();
  72. const theme = useTheme();
  73. const webviewRef = useRef<WebView>();
  74. const { setCollapsible } = useCollapsible();
  75. const collapsible = useCollapsibleHeader({
  76. config: { collapsedColor: theme.colors.surface, useNativeDriver: false },
  77. });
  78. const { containerPaddingTop, onScrollWithListener } = collapsible;
  79. const [currentInjectedJS, setCurrentInjectedJS] = useState(props.injectJS);
  80. useFocusEffect(
  81. useCallback(() => {
  82. setCollapsible(collapsible);
  83. BackHandler.addEventListener(
  84. 'hardwareBackPress',
  85. onBackButtonPressAndroid
  86. );
  87. return () => {
  88. BackHandler.removeEventListener(
  89. 'hardwareBackPress',
  90. onBackButtonPressAndroid
  91. );
  92. };
  93. // eslint-disable-next-line react-hooks/exhaustive-deps
  94. }, [collapsible, setCollapsible])
  95. );
  96. useLayoutEffect(() => {
  97. navigation.setOptions({
  98. headerRight: props.showAdvancedControls
  99. ? getAdvancedButtons
  100. : getBasicButton,
  101. });
  102. // eslint-disable-next-line react-hooks/exhaustive-deps
  103. }, [navigation, props.showAdvancedControls]);
  104. useEffect(() => {
  105. if (props.injectJS && props.injectJS !== currentInjectedJS) {
  106. injectJavaScript(props.injectJS);
  107. setCurrentInjectedJS(props.injectJS);
  108. }
  109. // eslint-disable-next-line react-hooks/exhaustive-deps
  110. }, [props.injectJS]);
  111. const onBackButtonPressAndroid = () => {
  112. if (canGoBack) {
  113. onGoBackClicked();
  114. return true;
  115. }
  116. return false;
  117. };
  118. const getBasicButton = () => {
  119. return (
  120. <MaterialHeaderButtons>
  121. <Item
  122. title={'refresh'}
  123. iconName={'refresh'}
  124. onPress={onRefreshClicked}
  125. />
  126. <Item
  127. title={i18n.t('general.openInBrowser')}
  128. iconName={'open-in-new'}
  129. onPress={onOpenClicked}
  130. />
  131. </MaterialHeaderButtons>
  132. );
  133. };
  134. const getAdvancedButtons = () => {
  135. return (
  136. <MaterialHeaderButtons>
  137. <Item title="refresh" iconName="refresh" onPress={onRefreshClicked} />
  138. <OverflowMenu
  139. style={styles.overflow}
  140. OverflowIcon={
  141. <MaterialCommunityIcons
  142. name="dots-vertical"
  143. size={26}
  144. color={theme.colors.text}
  145. />
  146. }
  147. >
  148. <HiddenItem
  149. title={i18n.t('general.goBack')}
  150. onPress={onGoBackClicked}
  151. />
  152. <HiddenItem
  153. title={i18n.t('general.goForward')}
  154. onPress={onGoForwardClicked}
  155. />
  156. <Divider />
  157. <HiddenItem
  158. title={i18n.t('general.openInBrowser')}
  159. onPress={onOpenClicked}
  160. />
  161. </OverflowMenu>
  162. </MaterialHeaderButtons>
  163. );
  164. };
  165. const getRenderLoading = () => <BasicLoadingScreen isAbsolute={true} />;
  166. /**
  167. * Gets the javascript needed to generate a padding on top of the page
  168. * This adds padding to the body and runs the custom padding function given in props
  169. *
  170. * @param padding The padding to add in pixels
  171. * @returns {string}
  172. */
  173. const getJavascriptPadding = (padding: number) => {
  174. const customPadding =
  175. props.customPaddingFunction != null
  176. ? props.customPaddingFunction(padding)
  177. : '';
  178. return `document.getElementsByTagName('body')[0].style.paddingTop = '${padding}px';${customPadding}true;`;
  179. };
  180. const onRefreshClicked = () => {
  181. //@ts-ignore
  182. if (webviewRef.current) {
  183. //@ts-ignore
  184. webviewRef.current.reload();
  185. }
  186. };
  187. const onGoBackClicked = () => {
  188. //@ts-ignore
  189. if (webviewRef.current) {
  190. //@ts-ignore
  191. webviewRef.current.goBack();
  192. }
  193. };
  194. const onGoForwardClicked = () => {
  195. //@ts-ignore
  196. if (webviewRef.current) {
  197. //@ts-ignore
  198. webviewRef.current.goForward();
  199. }
  200. };
  201. const onOpenClicked = () => Linking.openURL(currentUrl);
  202. const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
  203. if (props.onScroll) {
  204. props.onScroll(event);
  205. }
  206. };
  207. const injectJavaScript = (script: string) => {
  208. //@ts-ignore
  209. if (webviewRef.current) {
  210. //@ts-ignore
  211. webviewRef.current.injectJavaScript(script);
  212. }
  213. };
  214. return (
  215. <AnimatedWebView
  216. ref={webviewRef}
  217. source={{ uri: props.url }}
  218. startInLoadingState={true}
  219. injectedJavaScript={props.initialJS}
  220. javaScriptEnabled={true}
  221. renderLoading={getRenderLoading}
  222. renderError={() => (
  223. <ErrorView
  224. errorCode={ERROR_TYPE.CONNECTION_ERROR}
  225. onRefresh={onRefreshClicked}
  226. />
  227. )}
  228. onNavigationStateChange={(navState) => {
  229. setCurrentUrl(navState.url);
  230. setCanGoBack(navState.canGoBack);
  231. }}
  232. onMessage={props.onMessage}
  233. onLoad={() => injectJavaScript(getJavascriptPadding(containerPaddingTop))}
  234. // Animations
  235. onScroll={onScrollWithListener(onScroll)}
  236. />
  237. );
  238. }
  239. export default WebViewScreen;