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