Application Android et IOS pour l'amicale des élèves
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.js 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // @flow
  2. import * as React from 'react';
  3. import WebView from 'react-native-webview';
  4. import {
  5. Divider,
  6. HiddenItem,
  7. OverflowMenu,
  8. } from 'react-navigation-header-buttons';
  9. import i18n from 'i18n-js';
  10. import {Animated, BackHandler, Linking} from 'react-native';
  11. import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
  12. import {withTheme} from 'react-native-paper';
  13. import {StackNavigationProp} from '@react-navigation/stack';
  14. import {Collapsible} from 'react-navigation-collapsible';
  15. import type {CustomTheme} from '../../managers/ThemeManager';
  16. import {withCollapsible} from '../../utils/withCollapsible';
  17. import MaterialHeaderButtons, {Item} from '../Overrides/CustomHeaderButton';
  18. import {ERROR_TYPE} from '../../utils/WebData';
  19. import ErrorView from './ErrorView';
  20. import BasicLoadingScreen from './BasicLoadingScreen';
  21. type PropsType = {
  22. navigation: StackNavigationProp,
  23. theme: CustomTheme,
  24. url: string,
  25. collapsibleStack: Collapsible,
  26. onMessage: (event: {nativeEvent: {data: string}}) => void,
  27. onScroll: (event: SyntheticEvent<EventTarget>) => void,
  28. customJS?: string,
  29. customPaddingFunction?: null | ((padding: number) => string),
  30. showAdvancedControls?: boolean,
  31. };
  32. const AnimatedWebView = Animated.createAnimatedComponent(WebView);
  33. /**
  34. * Class defining a webview screen.
  35. */
  36. class WebViewScreen extends React.PureComponent<PropsType> {
  37. static defaultProps = {
  38. customJS: '',
  39. showAdvancedControls: true,
  40. customPaddingFunction: null,
  41. };
  42. webviewRef: {current: null | WebView};
  43. canGoBack: boolean;
  44. constructor() {
  45. super();
  46. this.webviewRef = React.createRef();
  47. this.canGoBack = false;
  48. }
  49. /**
  50. * Creates header buttons and listens to events after mounting
  51. */
  52. componentDidMount() {
  53. const {props} = this;
  54. props.navigation.setOptions({
  55. headerRight: props.showAdvancedControls
  56. ? this.getAdvancedButtons
  57. : this.getBasicButton,
  58. });
  59. props.navigation.addListener('focus', () => {
  60. BackHandler.addEventListener(
  61. 'hardwareBackPress',
  62. this.onBackButtonPressAndroid,
  63. );
  64. });
  65. props.navigation.addListener('blur', () => {
  66. BackHandler.removeEventListener(
  67. 'hardwareBackPress',
  68. this.onBackButtonPressAndroid,
  69. );
  70. });
  71. }
  72. /**
  73. * Goes back on the webview or on the navigation stack if we cannot go back anymore
  74. *
  75. * @returns {boolean}
  76. */
  77. onBackButtonPressAndroid = (): boolean => {
  78. if (this.canGoBack) {
  79. this.onGoBackClicked();
  80. return true;
  81. }
  82. return false;
  83. };
  84. /**
  85. * Gets header refresh and open in browser buttons
  86. *
  87. * @return {*}
  88. */
  89. getBasicButton = (): React.Node => {
  90. return (
  91. <MaterialHeaderButtons>
  92. <Item
  93. title="refresh"
  94. iconName="refresh"
  95. onPress={this.onRefreshClicked}
  96. />
  97. <Item
  98. title={i18n.t('general.openInBrowser')}
  99. iconName="open-in-new"
  100. onPress={this.onOpenClicked}
  101. />
  102. </MaterialHeaderButtons>
  103. );
  104. };
  105. /**
  106. * Creates advanced header control buttons.
  107. * These buttons allows the user to refresh, go back, go forward and open in the browser.
  108. *
  109. * @returns {*}
  110. */
  111. getAdvancedButtons = (): React.Node => {
  112. const {props} = this;
  113. return (
  114. <MaterialHeaderButtons>
  115. <Item
  116. title="refresh"
  117. iconName="refresh"
  118. onPress={this.onRefreshClicked}
  119. />
  120. <OverflowMenu
  121. style={{marginHorizontal: 10}}
  122. OverflowIcon={
  123. <MaterialCommunityIcons
  124. name="dots-vertical"
  125. size={26}
  126. color={props.theme.colors.text}
  127. />
  128. }>
  129. <HiddenItem
  130. title={i18n.t('general.goBack')}
  131. onPress={this.onGoBackClicked}
  132. />
  133. <HiddenItem
  134. title={i18n.t('general.goForward')}
  135. onPress={this.onGoForwardClicked}
  136. />
  137. <Divider />
  138. <HiddenItem
  139. title={i18n.t('general.openInBrowser')}
  140. onPress={this.onOpenClicked}
  141. />
  142. </OverflowMenu>
  143. </MaterialHeaderButtons>
  144. );
  145. };
  146. /**
  147. * Gets the loading indicator
  148. *
  149. * @return {*}
  150. */
  151. getRenderLoading = (): React.Node => <BasicLoadingScreen isAbsolute />;
  152. /**
  153. * Gets the javascript needed to generate a padding on top of the page
  154. * This adds padding to the body and runs the custom padding function given in props
  155. *
  156. * @param padding The padding to add in pixels
  157. * @returns {string}
  158. */
  159. getJavascriptPadding(padding: number): string {
  160. const {props} = this;
  161. const customPadding =
  162. props.customPaddingFunction != null
  163. ? props.customPaddingFunction(padding)
  164. : '';
  165. return `document.getElementsByTagName('body')[0].style.paddingTop = '${padding}px';${customPadding}true;`;
  166. }
  167. /**
  168. * Callback to use when refresh button is clicked. Reloads the webview.
  169. */
  170. onRefreshClicked = () => {
  171. if (this.webviewRef.current != null) this.webviewRef.current.reload();
  172. };
  173. onGoBackClicked = () => {
  174. if (this.webviewRef.current != null) this.webviewRef.current.goBack();
  175. };
  176. onGoForwardClicked = () => {
  177. if (this.webviewRef.current != null) this.webviewRef.current.goForward();
  178. };
  179. onOpenClicked = () => {
  180. const {url} = this.props;
  181. Linking.openURL(url);
  182. };
  183. onScroll = (event: SyntheticEvent<EventTarget>) => {
  184. const {onScroll} = this.props;
  185. if (onScroll) onScroll(event);
  186. };
  187. /**
  188. * Injects the given javascript string into the web page
  189. *
  190. * @param script The script to inject
  191. */
  192. injectJavaScript = (script: string) => {
  193. if (this.webviewRef.current != null)
  194. this.webviewRef.current.injectJavaScript(script);
  195. };
  196. render(): React.Node {
  197. const {props} = this;
  198. const {containerPaddingTop, onScrollWithListener} = props.collapsibleStack;
  199. return (
  200. <AnimatedWebView
  201. ref={this.webviewRef}
  202. source={{uri: props.url}}
  203. startInLoadingState
  204. injectedJavaScript={props.customJS}
  205. javaScriptEnabled
  206. renderLoading={this.getRenderLoading}
  207. renderError={(): React.Node => (
  208. <ErrorView
  209. errorCode={ERROR_TYPE.CONNECTION_ERROR}
  210. onRefresh={this.onRefreshClicked}
  211. />
  212. )}
  213. onNavigationStateChange={(navState: {canGoBack: boolean}) => {
  214. this.canGoBack = navState.canGoBack;
  215. }}
  216. onMessage={props.onMessage}
  217. onLoad={() => {
  218. this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop));
  219. }}
  220. // Animations
  221. onScroll={onScrollWithListener(this.onScroll)}
  222. />
  223. );
  224. }
  225. }
  226. export default withCollapsible(withTheme(WebViewScreen));