Browse Source

chore(format): Android project formatting (#433)

Added an extremely simple `.editorconfig` I inferred from the main part of the project, then reformatted the codebase according to it. 🙂
Stanisław Chmiela 5 years ago
parent
commit
e697dff1d0

+ 6
- 0
android/.editorconfig View File

1
+[*]
2
+charset=utf-8
3
+end_of_line=lf
4
+insert_final_newline=false
5
+indent_style=space
6
+indent_size=2

+ 14
- 12
android/src/main/AndroidManifest.xml View File

1
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.reactnativecommunity.webview">
2
-    <application>
3
-        <provider
4
-        android:name=".RNCWebViewFileProvider"
5
-        android:authorities="${applicationId}.fileprovider"
6
-        android:exported="false"
7
-        android:grantUriPermissions="true">
8
-            <meta-data
9
-                android:name="android.support.FILE_PROVIDER_PATHS"
10
-                android:resource="@xml/file_provider_paths" />
11
-        </provider>
12
-    </application>
1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+  package="com.reactnativecommunity.webview">
3
+
4
+  <application>
5
+    <provider
6
+      android:name=".RNCWebViewFileProvider"
7
+      android:authorities="${applicationId}.fileprovider"
8
+      android:exported="false"
9
+      android:grantUriPermissions="true">
10
+      <meta-data
11
+        android:name="android.support.FILE_PROVIDER_PATHS"
12
+        android:resource="@xml/file_provider_paths" />
13
+    </provider>
14
+  </application>
13
 </manifest>
15
 </manifest>

+ 1
- 1
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewFileProvider.java View File

4
 
4
 
5
 /**
5
 /**
6
  * Providing a custom {@code FileProvider} prevents manifest {@code <provider>} name collisions.
6
  * Providing a custom {@code FileProvider} prevents manifest {@code <provider>} name collisions.
7
- *
7
+ * <p>
8
  * See https://developer.android.com/guide/topics/manifest/provider-element.html for details.
8
  * See https://developer.android.com/guide/topics/manifest/provider-element.html for details.
9
  */
9
  */
10
 public class RNCWebViewFileProvider extends FileProvider {
10
 public class RNCWebViewFileProvider extends FileProvider {

+ 308
- 322
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java View File

4
 import android.annotation.TargetApi;
4
 import android.annotation.TargetApi;
5
 import android.app.DownloadManager;
5
 import android.app.DownloadManager;
6
 import android.content.Context;
6
 import android.content.Context;
7
-
8
-import com.facebook.react.uimanager.UIManagerModule;
9
-
10
-import java.net.MalformedURLException;
11
-import java.net.URL;
12
-import java.util.LinkedList;
13
-import java.util.List;
14
-import java.util.regex.Pattern;
15
-import javax.annotation.Nullable;
16
-
17
-import java.io.UnsupportedEncodingException;
18
-import java.net.URLEncoder;
19
-import java.util.ArrayList;
20
-import java.util.HashMap;
21
-import java.util.Locale;
22
-import java.util.Map;
23
-
24
-import android.content.ActivityNotFoundException;
25
 import android.content.Intent;
7
 import android.content.Intent;
26
 import android.graphics.Bitmap;
8
 import android.graphics.Bitmap;
27
 import android.net.Uri;
9
 import android.net.Uri;
43
 import android.webkit.WebView;
25
 import android.webkit.WebView;
44
 import android.webkit.WebViewClient;
26
 import android.webkit.WebViewClient;
45
 
27
 
46
-import com.facebook.common.logging.FLog;
47
 import com.facebook.react.bridge.Arguments;
28
 import com.facebook.react.bridge.Arguments;
48
 import com.facebook.react.bridge.LifecycleEventListener;
29
 import com.facebook.react.bridge.LifecycleEventListener;
49
 import com.facebook.react.bridge.ReactContext;
30
 import com.facebook.react.bridge.ReactContext;
52
 import com.facebook.react.bridge.ReadableMapKeySetIterator;
33
 import com.facebook.react.bridge.ReadableMapKeySetIterator;
53
 import com.facebook.react.bridge.WritableMap;
34
 import com.facebook.react.bridge.WritableMap;
54
 import com.facebook.react.common.MapBuilder;
35
 import com.facebook.react.common.MapBuilder;
55
-import com.facebook.react.common.ReactConstants;
56
 import com.facebook.react.common.build.ReactBuildConfig;
36
 import com.facebook.react.common.build.ReactBuildConfig;
57
 import com.facebook.react.module.annotations.ReactModule;
37
 import com.facebook.react.module.annotations.ReactModule;
58
 import com.facebook.react.uimanager.SimpleViewManager;
38
 import com.facebook.react.uimanager.SimpleViewManager;
59
 import com.facebook.react.uimanager.ThemedReactContext;
39
 import com.facebook.react.uimanager.ThemedReactContext;
40
+import com.facebook.react.uimanager.UIManagerModule;
60
 import com.facebook.react.uimanager.annotations.ReactProp;
41
 import com.facebook.react.uimanager.annotations.ReactProp;
61
 import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
42
 import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
62
 import com.facebook.react.uimanager.events.Event;
43
 import com.facebook.react.uimanager.events.Event;
63
 import com.facebook.react.uimanager.events.EventDispatcher;
44
 import com.facebook.react.uimanager.events.EventDispatcher;
64
-import com.facebook.react.uimanager.events.RCTEventEmitter;
65
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
45
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
66
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
46
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
47
+import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
67
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
48
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
68
 import com.reactnativecommunity.webview.events.TopMessageEvent;
49
 import com.reactnativecommunity.webview.events.TopMessageEvent;
69
-import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
70
 import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
50
 import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
51
+
52
+import org.json.JSONException;
53
+import org.json.JSONObject;
54
+
71
 import java.io.UnsupportedEncodingException;
55
 import java.io.UnsupportedEncodingException;
72
-import java.util.ArrayList;
56
+import java.net.MalformedURLException;
57
+import java.net.URL;
58
+import java.net.URLEncoder;
73
 import java.util.HashMap;
59
 import java.util.HashMap;
74
 import java.util.Locale;
60
 import java.util.Locale;
75
 import java.util.Map;
61
 import java.util.Map;
62
+
76
 import javax.annotation.Nullable;
63
 import javax.annotation.Nullable;
77
-import org.json.JSONException;
78
-import org.json.JSONObject;
79
 
64
 
80
 /**
65
 /**
81
  * Manages instances of {@link WebView}
66
  * Manages instances of {@link WebView}
82
- *
67
+ * <p>
83
  * Can accept following commands:
68
  * Can accept following commands:
84
- *  - GO_BACK
85
- *  - GO_FORWARD
86
- *  - RELOAD
87
- *  - LOAD_URL
88
- *
69
+ * - GO_BACK
70
+ * - GO_FORWARD
71
+ * - RELOAD
72
+ * - LOAD_URL
73
+ * <p>
89
  * {@link WebView} instances could emit following direct events:
74
  * {@link WebView} instances could emit following direct events:
90
- *  - topLoadingFinish
91
- *  - topLoadingStart
92
- *  - topLoadingStart
93
- *  - topLoadingProgress
94
- *  - topShouldStartLoadWithRequest
95
- *
75
+ * - topLoadingFinish
76
+ * - topLoadingStart
77
+ * - topLoadingStart
78
+ * - topLoadingProgress
79
+ * - topShouldStartLoadWithRequest
80
+ * <p>
96
  * Each event will carry the following properties:
81
  * Each event will carry the following properties:
97
- *  - target - view's react tag
98
- *  - url - url set for the webview
99
- *  - loading - whether webview is in a loading state
100
- *  - title - title of the current page
101
- *  - canGoBack - boolean, whether there is anything on a history stack to go back
102
- *  - canGoForward - boolean, whether it is possible to request GO_FORWARD command
82
+ * - target - view's react tag
83
+ * - url - url set for the webview
84
+ * - loading - whether webview is in a loading state
85
+ * - title - title of the current page
86
+ * - canGoBack - boolean, whether there is anything on a history stack to go back
87
+ * - canGoForward - boolean, whether it is possible to request GO_FORWARD command
103
  */
88
  */
104
 @ReactModule(name = RNCWebViewManager.REACT_CLASS)
89
 @ReactModule(name = RNCWebViewManager.REACT_CLASS)
105
 public class RNCWebViewManager extends SimpleViewManager<WebView> {
90
 public class RNCWebViewManager extends SimpleViewManager<WebView> {
106
 
91
 
107
-  protected static final String REACT_CLASS = "RNCWebView";
108
-  private RNCWebViewPackage aPackage;
109
-
110
-  protected static final String HTML_ENCODING = "UTF-8";
111
-  protected static final String HTML_MIME_TYPE = "text/html";
112
-  protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
113
-
114
-  protected static final String HTTP_METHOD_POST = "POST";
115
-
116
   public static final int COMMAND_GO_BACK = 1;
92
   public static final int COMMAND_GO_BACK = 1;
117
   public static final int COMMAND_GO_FORWARD = 2;
93
   public static final int COMMAND_GO_FORWARD = 2;
118
   public static final int COMMAND_RELOAD = 3;
94
   public static final int COMMAND_RELOAD = 3;
120
   public static final int COMMAND_POST_MESSAGE = 5;
96
   public static final int COMMAND_POST_MESSAGE = 5;
121
   public static final int COMMAND_INJECT_JAVASCRIPT = 6;
97
   public static final int COMMAND_INJECT_JAVASCRIPT = 6;
122
   public static final int COMMAND_LOAD_URL = 7;
98
   public static final int COMMAND_LOAD_URL = 7;
123
-
99
+  protected static final String REACT_CLASS = "RNCWebView";
100
+  protected static final String HTML_ENCODING = "UTF-8";
101
+  protected static final String HTML_MIME_TYPE = "text/html";
102
+  protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
103
+  protected static final String HTTP_METHOD_POST = "POST";
124
   // Use `webView.loadUrl("about:blank")` to reliably reset the view
104
   // Use `webView.loadUrl("about:blank")` to reliably reset the view
125
   // state and release page resources (including any running JavaScript).
105
   // state and release page resources (including any running JavaScript).
126
   protected static final String BLANK_URL = "about:blank";
106
   protected static final String BLANK_URL = "about:blank";
127
-
128
   protected WebViewConfig mWebViewConfig;
107
   protected WebViewConfig mWebViewConfig;
129
-
130
-  protected static class RNCWebViewClient extends WebViewClient {
131
-
132
-    protected boolean mLastLoadFailed = false;
133
-    protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
134
-
135
-    @Override
136
-    public void onPageFinished(WebView webView, String url) {
137
-      super.onPageFinished(webView, url);
138
-
139
-      if (!mLastLoadFailed) {
140
-        RNCWebView reactWebView = (RNCWebView) webView;
141
-
142
-        reactWebView.callInjectedJavaScript();
143
-
144
-        emitFinishEvent(webView, url);
145
-      }
146
-    }
147
-
148
-    @Override
149
-    public void onPageStarted(WebView webView, String url, Bitmap favicon) {
150
-      super.onPageStarted(webView, url, favicon);
151
-      mLastLoadFailed = false;
152
-
153
-      dispatchEvent(
154
-          webView,
155
-          new TopLoadingStartEvent(
156
-              webView.getId(),
157
-              createWebViewEvent(webView, url)));
158
-    }
159
-
160
-    @Override
161
-    public boolean shouldOverrideUrlLoading(WebView view, String url) {
162
-      dispatchEvent(
163
-          view,
164
-          new TopShouldStartLoadWithRequestEvent(
165
-                  view.getId(),
166
-                  createWebViewEvent(view, url)));
167
-      return true;
168
-    }
169
-
170
-
171
-    @TargetApi(Build.VERSION_CODES.N)
172
-    @Override
173
-    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
174
-      final String url = request.getUrl().toString();
175
-      return this.shouldOverrideUrlLoading(view, url);
176
-    }
177
-
178
-    @Override
179
-    public void onReceivedError(
180
-        WebView webView,
181
-        int errorCode,
182
-        String description,
183
-        String failingUrl) {
184
-      super.onReceivedError(webView, errorCode, description, failingUrl);
185
-      mLastLoadFailed = true;
186
-
187
-      // In case of an error JS side expect to get a finish event first, and then get an error event
188
-      // Android WebView does it in the opposite way, so we need to simulate that behavior
189
-      emitFinishEvent(webView, failingUrl);
190
-
191
-      WritableMap eventData = createWebViewEvent(webView, failingUrl);
192
-      eventData.putDouble("code", errorCode);
193
-      eventData.putString("description", description);
194
-
195
-      dispatchEvent(
196
-          webView,
197
-          new TopLoadingErrorEvent(webView.getId(), eventData));
198
-    }
199
-
200
-    protected void emitFinishEvent(WebView webView, String url) {
201
-      dispatchEvent(
202
-          webView,
203
-          new TopLoadingFinishEvent(
204
-              webView.getId(),
205
-              createWebViewEvent(webView, url)));
206
-    }
207
-
208
-    protected WritableMap createWebViewEvent(WebView webView, String url) {
209
-      WritableMap event = Arguments.createMap();
210
-      event.putDouble("target", webView.getId());
211
-      // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
212
-      // like onPageFinished
213
-      event.putString("url", url);
214
-      event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
215
-      event.putString("title", webView.getTitle());
216
-      event.putBoolean("canGoBack", webView.canGoBack());
217
-      event.putBoolean("canGoForward", webView.canGoForward());
218
-      return event;
219
-    }
220
-
221
-    public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
222
-      mUrlPrefixesForDefaultIntent = specialUrls;
223
-    }
224
-  }
225
-
226
-  /**
227
-   * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
228
-   * to call {@link WebView#destroy} on activity destroy event and also to clear the client
229
-   */
230
-  protected static class RNCWebView extends WebView implements LifecycleEventListener {
231
-    protected @Nullable String injectedJS;
232
-    protected boolean messagingEnabled = false;
233
-    protected @Nullable RNCWebViewClient mRNCWebViewClient;
234
-    protected boolean sendContentSizeChangeEvents = false;
235
-    public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
236
-      this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
237
-    }
238
-
239
-
240
-    protected class RNCWebViewBridge {
241
-      RNCWebView mContext;
242
-
243
-      RNCWebViewBridge(RNCWebView c) {
244
-        mContext = c;
245
-      }
246
-
247
-      /**
248
-       * This method is called whenever JavaScript running within the web view calls:
249
-       *   - window[JAVASCRIPT_INTERFACE].postMessage
250
-       */
251
-      @JavascriptInterface
252
-      public void postMessage(String message) {
253
-        mContext.onMessage(message);
254
-      }
255
-    }
256
-
257
-    /**
258
-     * WebView must be created with an context of the current activity
259
-     *
260
-     * Activity Context is required for creation of dialogs internally by WebView
261
-     * Reactive Native needed for access to ReactNative internal system functionality
262
-     *
263
-     */
264
-    public RNCWebView(ThemedReactContext reactContext) {
265
-      super(reactContext);
266
-    }
267
-
268
-    @Override
269
-    public void onHostResume() {
270
-      // do nothing
271
-    }
272
-
273
-    @Override
274
-    public void onHostPause() {
275
-      // do nothing
276
-    }
277
-
278
-    @Override
279
-    public void onHostDestroy() {
280
-      cleanupCallbacksAndDestroy();
281
-    }
282
-
283
-    @Override
284
-    protected void onSizeChanged(int w, int h, int ow, int oh) {
285
-      super.onSizeChanged(w, h, ow, oh);
286
-
287
-      if (sendContentSizeChangeEvents) {
288
-        dispatchEvent(
289
-          this,
290
-          new ContentSizeChangeEvent(
291
-            this.getId(),
292
-            w,
293
-            h
294
-          )
295
-        );
296
-      }
297
-    }
298
-
299
-    @Override
300
-    public void setWebViewClient(WebViewClient client) {
301
-      super.setWebViewClient(client);
302
-      mRNCWebViewClient = (RNCWebViewClient)client;
303
-    }
304
-
305
-    public @Nullable RNCWebViewClient getRNCWebViewClient() {
306
-      return mRNCWebViewClient;
307
-    }
308
-
309
-    public void setInjectedJavaScript(@Nullable String js) {
310
-      injectedJS = js;
311
-    }
312
-
313
-    protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
314
-      return new RNCWebViewBridge(webView);
315
-    }
316
-
317
-    @SuppressLint("AddJavascriptInterface")
318
-    public void setMessagingEnabled(boolean enabled) {
319
-      if (messagingEnabled == enabled) {
320
-        return;
321
-      }
322
-
323
-      messagingEnabled = enabled;
324
-
325
-      if (enabled) {
326
-        addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
327
-      } else {
328
-        removeJavascriptInterface(JAVASCRIPT_INTERFACE);
329
-      }
330
-    }
331
-
332
-    protected void evaluateJavascriptWithFallback(String script) {
333
-      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
334
-        evaluateJavascript(script, null);
335
-        return;
336
-      }
337
-
338
-      try {
339
-        loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
340
-      } catch (UnsupportedEncodingException e) {
341
-        // UTF-8 should always be supported
342
-        throw new RuntimeException(e);
343
-      }
344
-    }
345
-
346
-    public void callInjectedJavaScript() {
347
-      if (getSettings().getJavaScriptEnabled() &&
348
-          injectedJS != null &&
349
-          !TextUtils.isEmpty(injectedJS)) {
350
-        evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
351
-      }
352
-    }
353
-
354
-    public void onMessage(String message) {
355
-      dispatchEvent(this, new TopMessageEvent(this.getId(), message));
356
-    }
357
-
358
-    protected void cleanupCallbacksAndDestroy() {
359
-      setWebViewClient(null);
360
-      destroy();
361
-    }
362
-  }
108
+  private RNCWebViewPackage aPackage;
363
 
109
 
364
   public RNCWebViewManager() {
110
   public RNCWebViewManager() {
365
     mWebViewConfig = new WebViewConfig() {
111
     mWebViewConfig = new WebViewConfig() {
372
     mWebViewConfig = webViewConfig;
118
     mWebViewConfig = webViewConfig;
373
   }
119
   }
374
 
120
 
121
+  protected static void dispatchEvent(WebView webView, Event event) {
122
+    ReactContext reactContext = (ReactContext) webView.getContext();
123
+    EventDispatcher eventDispatcher =
124
+      reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
125
+    eventDispatcher.dispatchEvent(event);
126
+  }
127
+
375
   @Override
128
   @Override
376
   public String getName() {
129
   public String getName() {
377
     return REACT_CLASS;
130
     return REACT_CLASS;
396
       }
149
       }
397
 
150
 
398
 
151
 
399
-    @Override
400
-    public void onProgressChanged(WebView webView, int newProgress) {
152
+      @Override
153
+      public void onProgressChanged(WebView webView, int newProgress) {
401
         super.onProgressChanged(webView, newProgress);
154
         super.onProgressChanged(webView, newProgress);
402
         WritableMap event = Arguments.createMap();
155
         WritableMap event = Arguments.createMap();
403
         event.putDouble("target", webView.getId());
156
         event.putDouble("target", webView.getId());
404
         event.putString("title", webView.getTitle());
157
         event.putString("title", webView.getTitle());
405
         event.putBoolean("canGoBack", webView.canGoBack());
158
         event.putBoolean("canGoBack", webView.canGoBack());
406
         event.putBoolean("canGoForward", webView.canGoForward());
159
         event.putBoolean("canGoForward", webView.canGoForward());
407
-        event.putDouble("progress", (float)newProgress/100);
160
+        event.putDouble("progress", (float) newProgress / 100);
408
         dispatchEvent(
161
         dispatchEvent(
409
-                  webView,
410
-                  new TopLoadingProgressEvent(
411
-                      webView.getId(),
412
-                      event));
413
-    }
162
+          webView,
163
+          new TopLoadingProgressEvent(
164
+            webView.getId(),
165
+            event));
166
+      }
414
 
167
 
415
       @Override
168
       @Override
416
       public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
169
       public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
420
       protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
173
       protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
421
         getModule().startPhotoPickerIntent(filePathCallback, acceptType);
174
         getModule().startPhotoPickerIntent(filePathCallback, acceptType);
422
       }
175
       }
176
+
423
       protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
177
       protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
424
         getModule().startPhotoPickerIntent(filePathCallback, "");
178
         getModule().startPhotoPickerIntent(filePathCallback, "");
425
       }
179
       }
180
+
426
       protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
181
       protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
427
         getModule().startPhotoPickerIntent(filePathCallback, acceptType);
182
         getModule().startPhotoPickerIntent(filePathCallback, acceptType);
428
       }
183
       }
453
 
208
 
454
     // Fixes broken full-screen modals/galleries due to body height being 0.
209
     // Fixes broken full-screen modals/galleries due to body height being 0.
455
     webView.setLayoutParams(
210
     webView.setLayoutParams(
456
-            new LayoutParams(LayoutParams.MATCH_PARENT,
457
-                LayoutParams.MATCH_PARENT));
211
+      new LayoutParams(LayoutParams.MATCH_PARENT,
212
+        LayoutParams.MATCH_PARENT));
458
 
213
 
459
     setGeolocationEnabled(webView, false);
214
     setGeolocationEnabled(webView, false);
460
     if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
215
     if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
516
   public void setShowsVerticalScrollIndicator(WebView view, boolean enabled) {
271
   public void setShowsVerticalScrollIndicator(WebView view, boolean enabled) {
517
     view.setVerticalScrollBarEnabled(enabled);
272
     view.setVerticalScrollBarEnabled(enabled);
518
   }
273
   }
519
-  
274
+
520
   @ReactProp(name = "cacheEnabled")
275
   @ReactProp(name = "cacheEnabled")
521
   public void setCacheEnabled(WebView view, boolean enabled) {
276
   public void setCacheEnabled(WebView view, boolean enabled) {
522
     if (enabled) {
277
     if (enabled) {
616
         String html = source.getString("html");
371
         String html = source.getString("html");
617
         if (source.hasKey("baseUrl")) {
372
         if (source.hasKey("baseUrl")) {
618
           view.loadDataWithBaseURL(
373
           view.loadDataWithBaseURL(
619
-              source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
374
+            source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
620
         } else {
375
         } else {
621
           view.loadData(html, HTML_MIME_TYPE + "; charset=" + HTML_ENCODING, null);
376
           view.loadData(html, HTML_MIME_TYPE + "; charset=" + HTML_ENCODING, null);
622
         }
377
         }
689
 
444
 
690
   @ReactProp(name = "urlPrefixesForDefaultIntent")
445
   @ReactProp(name = "urlPrefixesForDefaultIntent")
691
   public void setUrlPrefixesForDefaultIntent(
446
   public void setUrlPrefixesForDefaultIntent(
692
-      WebView view,
693
-      @Nullable ReadableArray urlPrefixesForDefaultIntent) {
447
+    WebView view,
448
+    @Nullable ReadableArray urlPrefixesForDefaultIntent) {
694
     RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
449
     RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
695
     if (client != null && urlPrefixesForDefaultIntent != null) {
450
     if (client != null && urlPrefixesForDefaultIntent != null) {
696
       client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
451
       client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
729
   }
484
   }
730
 
485
 
731
   @Override
486
   @Override
732
-  public @Nullable Map<String, Integer> getCommandsMap() {
487
+  public @Nullable
488
+  Map<String, Integer> getCommandsMap() {
733
     return MapBuilder.of(
489
     return MapBuilder.of(
734
-        "goBack", COMMAND_GO_BACK,
735
-        "goForward", COMMAND_GO_FORWARD,
736
-        "reload", COMMAND_RELOAD,
737
-        "stopLoading", COMMAND_STOP_LOADING,
738
-        "postMessage", COMMAND_POST_MESSAGE,
739
-        "injectJavaScript", COMMAND_INJECT_JAVASCRIPT,
740
-        "loadUrl", COMMAND_LOAD_URL
741
-      );
490
+      "goBack", COMMAND_GO_BACK,
491
+      "goForward", COMMAND_GO_FORWARD,
492
+      "reload", COMMAND_RELOAD,
493
+      "stopLoading", COMMAND_STOP_LOADING,
494
+      "postMessage", COMMAND_POST_MESSAGE,
495
+      "injectJavaScript", COMMAND_INJECT_JAVASCRIPT,
496
+      "loadUrl", COMMAND_LOAD_URL
497
+    );
742
   }
498
   }
743
 
499
 
744
   @Override
500
   @Override
765
             "var event;" +
521
             "var event;" +
766
             "var data = " + eventInitDict.toString() + ";" +
522
             "var data = " + eventInitDict.toString() + ";" +
767
             "try {" +
523
             "try {" +
768
-              "event = new MessageEvent('message', data);" +
524
+            "event = new MessageEvent('message', data);" +
769
             "} catch (e) {" +
525
             "} catch (e) {" +
770
-              "event = document.createEvent('MessageEvent');" +
771
-              "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
526
+            "event = document.createEvent('MessageEvent');" +
527
+            "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
772
             "}" +
528
             "}" +
773
             "document.dispatchEvent(event);" +
529
             "document.dispatchEvent(event);" +
774
-          "})();");
530
+            "})();");
775
         } catch (JSONException e) {
531
         } catch (JSONException e) {
776
           throw new RuntimeException(e);
532
           throw new RuntimeException(e);
777
         }
533
         }
796
     ((RNCWebView) webView).cleanupCallbacksAndDestroy();
552
     ((RNCWebView) webView).cleanupCallbacksAndDestroy();
797
   }
553
   }
798
 
554
 
799
-  protected static void dispatchEvent(WebView webView, Event event) {
800
-    ReactContext reactContext = (ReactContext) webView.getContext();
801
-    EventDispatcher eventDispatcher =
802
-      reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
803
-    eventDispatcher.dispatchEvent(event);
804
-  }
805
-
806
   public RNCWebViewPackage getPackage() {
555
   public RNCWebViewPackage getPackage() {
807
     return this.aPackage;
556
     return this.aPackage;
808
   }
557
   }
814
   public RNCWebViewModule getModule() {
563
   public RNCWebViewModule getModule() {
815
     return this.aPackage.getModule();
564
     return this.aPackage.getModule();
816
   }
565
   }
566
+
567
+  protected static class RNCWebViewClient extends WebViewClient {
568
+
569
+    protected boolean mLastLoadFailed = false;
570
+    protected @Nullable
571
+    ReadableArray mUrlPrefixesForDefaultIntent;
572
+
573
+    @Override
574
+    public void onPageFinished(WebView webView, String url) {
575
+      super.onPageFinished(webView, url);
576
+
577
+      if (!mLastLoadFailed) {
578
+        RNCWebView reactWebView = (RNCWebView) webView;
579
+
580
+        reactWebView.callInjectedJavaScript();
581
+
582
+        emitFinishEvent(webView, url);
583
+      }
584
+    }
585
+
586
+    @Override
587
+    public void onPageStarted(WebView webView, String url, Bitmap favicon) {
588
+      super.onPageStarted(webView, url, favicon);
589
+      mLastLoadFailed = false;
590
+
591
+      dispatchEvent(
592
+        webView,
593
+        new TopLoadingStartEvent(
594
+          webView.getId(),
595
+          createWebViewEvent(webView, url)));
596
+    }
597
+
598
+    @Override
599
+    public boolean shouldOverrideUrlLoading(WebView view, String url) {
600
+      dispatchEvent(
601
+        view,
602
+        new TopShouldStartLoadWithRequestEvent(
603
+          view.getId(),
604
+          createWebViewEvent(view, url)));
605
+      return true;
606
+    }
607
+
608
+
609
+    @TargetApi(Build.VERSION_CODES.N)
610
+    @Override
611
+    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
612
+      final String url = request.getUrl().toString();
613
+      return this.shouldOverrideUrlLoading(view, url);
614
+    }
615
+
616
+    @Override
617
+    public void onReceivedError(
618
+      WebView webView,
619
+      int errorCode,
620
+      String description,
621
+      String failingUrl) {
622
+      super.onReceivedError(webView, errorCode, description, failingUrl);
623
+      mLastLoadFailed = true;
624
+
625
+      // In case of an error JS side expect to get a finish event first, and then get an error event
626
+      // Android WebView does it in the opposite way, so we need to simulate that behavior
627
+      emitFinishEvent(webView, failingUrl);
628
+
629
+      WritableMap eventData = createWebViewEvent(webView, failingUrl);
630
+      eventData.putDouble("code", errorCode);
631
+      eventData.putString("description", description);
632
+
633
+      dispatchEvent(
634
+        webView,
635
+        new TopLoadingErrorEvent(webView.getId(), eventData));
636
+    }
637
+
638
+    protected void emitFinishEvent(WebView webView, String url) {
639
+      dispatchEvent(
640
+        webView,
641
+        new TopLoadingFinishEvent(
642
+          webView.getId(),
643
+          createWebViewEvent(webView, url)));
644
+    }
645
+
646
+    protected WritableMap createWebViewEvent(WebView webView, String url) {
647
+      WritableMap event = Arguments.createMap();
648
+      event.putDouble("target", webView.getId());
649
+      // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
650
+      // like onPageFinished
651
+      event.putString("url", url);
652
+      event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
653
+      event.putString("title", webView.getTitle());
654
+      event.putBoolean("canGoBack", webView.canGoBack());
655
+      event.putBoolean("canGoForward", webView.canGoForward());
656
+      return event;
657
+    }
658
+
659
+    public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
660
+      mUrlPrefixesForDefaultIntent = specialUrls;
661
+    }
662
+  }
663
+
664
+  /**
665
+   * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
666
+   * to call {@link WebView#destroy} on activity destroy event and also to clear the client
667
+   */
668
+  protected static class RNCWebView extends WebView implements LifecycleEventListener {
669
+    protected @Nullable
670
+    String injectedJS;
671
+    protected boolean messagingEnabled = false;
672
+    protected @Nullable
673
+    RNCWebViewClient mRNCWebViewClient;
674
+    protected boolean sendContentSizeChangeEvents = false;
675
+
676
+    /**
677
+     * WebView must be created with an context of the current activity
678
+     * <p>
679
+     * Activity Context is required for creation of dialogs internally by WebView
680
+     * Reactive Native needed for access to ReactNative internal system functionality
681
+     */
682
+    public RNCWebView(ThemedReactContext reactContext) {
683
+      super(reactContext);
684
+    }
685
+
686
+    public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
687
+      this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
688
+    }
689
+
690
+    @Override
691
+    public void onHostResume() {
692
+      // do nothing
693
+    }
694
+
695
+    @Override
696
+    public void onHostPause() {
697
+      // do nothing
698
+    }
699
+
700
+    @Override
701
+    public void onHostDestroy() {
702
+      cleanupCallbacksAndDestroy();
703
+    }
704
+
705
+    @Override
706
+    protected void onSizeChanged(int w, int h, int ow, int oh) {
707
+      super.onSizeChanged(w, h, ow, oh);
708
+
709
+      if (sendContentSizeChangeEvents) {
710
+        dispatchEvent(
711
+          this,
712
+          new ContentSizeChangeEvent(
713
+            this.getId(),
714
+            w,
715
+            h
716
+          )
717
+        );
718
+      }
719
+    }
720
+
721
+    @Override
722
+    public void setWebViewClient(WebViewClient client) {
723
+      super.setWebViewClient(client);
724
+      mRNCWebViewClient = (RNCWebViewClient) client;
725
+    }
726
+
727
+    public @Nullable
728
+    RNCWebViewClient getRNCWebViewClient() {
729
+      return mRNCWebViewClient;
730
+    }
731
+
732
+    public void setInjectedJavaScript(@Nullable String js) {
733
+      injectedJS = js;
734
+    }
735
+
736
+    protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
737
+      return new RNCWebViewBridge(webView);
738
+    }
739
+
740
+    @SuppressLint("AddJavascriptInterface")
741
+    public void setMessagingEnabled(boolean enabled) {
742
+      if (messagingEnabled == enabled) {
743
+        return;
744
+      }
745
+
746
+      messagingEnabled = enabled;
747
+
748
+      if (enabled) {
749
+        addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
750
+      } else {
751
+        removeJavascriptInterface(JAVASCRIPT_INTERFACE);
752
+      }
753
+    }
754
+
755
+    protected void evaluateJavascriptWithFallback(String script) {
756
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
757
+        evaluateJavascript(script, null);
758
+        return;
759
+      }
760
+
761
+      try {
762
+        loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
763
+      } catch (UnsupportedEncodingException e) {
764
+        // UTF-8 should always be supported
765
+        throw new RuntimeException(e);
766
+      }
767
+    }
768
+
769
+    public void callInjectedJavaScript() {
770
+      if (getSettings().getJavaScriptEnabled() &&
771
+        injectedJS != null &&
772
+        !TextUtils.isEmpty(injectedJS)) {
773
+        evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
774
+      }
775
+    }
776
+
777
+    public void onMessage(String message) {
778
+      dispatchEvent(this, new TopMessageEvent(this.getId(), message));
779
+    }
780
+
781
+    protected void cleanupCallbacksAndDestroy() {
782
+      setWebViewClient(null);
783
+      destroy();
784
+    }
785
+
786
+    protected class RNCWebViewBridge {
787
+      RNCWebView mContext;
788
+
789
+      RNCWebViewBridge(RNCWebView c) {
790
+        mContext = c;
791
+      }
792
+
793
+      /**
794
+       * This method is called whenever JavaScript running within the web view calls:
795
+       * - window[JAVASCRIPT_INTERFACE].postMessage
796
+       */
797
+      @JavascriptInterface
798
+      public void postMessage(String message) {
799
+        mContext.onMessage(message);
800
+      }
801
+    }
802
+  }
817
 }
803
 }

+ 106
- 110
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java View File

1
-
2
 package com.reactnativecommunity.webview;
1
 package com.reactnativecommunity.webview;
3
 
2
 
4
 import android.Manifest;
3
 import android.Manifest;
37
 
36
 
38
 public class RNCWebViewModule extends ReactContextBaseJavaModule implements ActivityEventListener {
37
 public class RNCWebViewModule extends ReactContextBaseJavaModule implements ActivityEventListener {
39
 
38
 
40
-  private final ReactApplicationContext reactContext;
41
-  private RNCWebViewPackage aPackage;
42
-
43
   private static final int PICKER = 1;
39
   private static final int PICKER = 1;
44
   private static final int PICKER_LEGACY = 3;
40
   private static final int PICKER_LEGACY = 3;
45
-
41
+  private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
42
+  final String DEFAULT_MIME_TYPES = "*/*";
43
+  private final ReactApplicationContext reactContext;
44
+  private RNCWebViewPackage aPackage;
46
   private ValueCallback<Uri> filePathCallbackLegacy;
45
   private ValueCallback<Uri> filePathCallbackLegacy;
47
   private ValueCallback<Uri[]> filePathCallback;
46
   private ValueCallback<Uri[]> filePathCallback;
48
   private Uri outputFileUri;
47
   private Uri outputFileUri;
49
-
50
   private DownloadManager.Request downloadRequest;
48
   private DownloadManager.Request downloadRequest;
51
-  private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
52
-
53
-  final String DEFAULT_MIME_TYPES = "*/*";
49
+  private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
50
+    @Override
51
+    public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
52
+      switch (requestCode) {
53
+        case FILE_DOWNLOAD_PERMISSION_REQUEST: {
54
+          // If request is cancelled, the result arrays are empty.
55
+          if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
56
+            if (downloadRequest != null) {
57
+              downloadFile();
58
+            }
59
+          } else {
60
+            Toast.makeText(getCurrentActivity().getApplicationContext(), "Cannot download files as permission was denied. Please provide permission to write to storage, in order to download files.", Toast.LENGTH_LONG).show();
61
+          }
62
+          return true;
63
+        }
64
+      }
65
+      return false;
66
+    }
67
+  };
54
 
68
 
55
   public RNCWebViewModule(ReactApplicationContext reactContext) {
69
   public RNCWebViewModule(ReactApplicationContext reactContext) {
56
     super(reactContext);
70
     super(reactContext);
65
 
79
 
66
   @ReactMethod
80
   @ReactMethod
67
   public void isFileUploadSupported(final Promise promise) {
81
   public void isFileUploadSupported(final Promise promise) {
68
-      Boolean result = false;
69
-      int current = Build.VERSION.SDK_INT;
70
-      if (current >= Build.VERSION_CODES.LOLLIPOP) {
71
-          result = true;
72
-      }
73
-      if (current >= Build.VERSION_CODES.JELLY_BEAN && current <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
74
-          result = true;
75
-      }
76
-      promise.resolve(result);
82
+    Boolean result = false;
83
+    int current = Build.VERSION.SDK_INT;
84
+    if (current >= Build.VERSION_CODES.LOLLIPOP) {
85
+      result = true;
86
+    }
87
+    if (current >= Build.VERSION_CODES.JELLY_BEAN && current <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
88
+      result = true;
89
+    }
90
+    promise.resolve(result);
77
   }
91
   }
78
 
92
 
79
   public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
93
   public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
80
 
94
 
81
     if (filePathCallback == null && filePathCallbackLegacy == null) {
95
     if (filePathCallback == null && filePathCallbackLegacy == null) {
82
-        return;
96
+      return;
83
     }
97
     }
84
 
98
 
85
     // based off of which button was pressed, we get an activity result and a file
99
     // based off of which button was pressed, we get an activity result and a file
86
     // the camera activity doesn't properly return the filename* (I think?) so we use
100
     // the camera activity doesn't properly return the filename* (I think?) so we use
87
     // this filename instead
101
     // this filename instead
88
     switch (requestCode) {
102
     switch (requestCode) {
89
-    case PICKER:
103
+      case PICKER:
90
         if (resultCode != RESULT_OK) {
104
         if (resultCode != RESULT_OK) {
91
-            if (filePathCallback != null) {
92
-                filePathCallback.onReceiveValue(null);
93
-            }
105
+          if (filePathCallback != null) {
106
+            filePathCallback.onReceiveValue(null);
107
+          }
94
         } else {
108
         } else {
95
-            Uri result[] = this.getSelectedFiles(data, resultCode);
96
-            if (result != null) {
97
-                filePathCallback.onReceiveValue(result);
98
-            } else {
99
-                filePathCallback.onReceiveValue(new Uri[] { outputFileUri });
100
-            }
109
+          Uri result[] = this.getSelectedFiles(data, resultCode);
110
+          if (result != null) {
111
+            filePathCallback.onReceiveValue(result);
112
+          } else {
113
+            filePathCallback.onReceiveValue(new Uri[]{outputFileUri});
114
+          }
101
         }
115
         }
102
         break;
116
         break;
103
-    case PICKER_LEGACY:
117
+      case PICKER_LEGACY:
104
         Uri result = resultCode != Activity.RESULT_OK ? null : data == null ? outputFileUri : data.getData();
118
         Uri result = resultCode != Activity.RESULT_OK ? null : data == null ? outputFileUri : data.getData();
105
         filePathCallbackLegacy.onReceiveValue(result);
119
         filePathCallbackLegacy.onReceiveValue(result);
106
         break;
120
         break;
107
 
121
 
108
     }
122
     }
109
     filePathCallback = null;
123
     filePathCallback = null;
110
-    filePathCallbackLegacy= null;
124
+    filePathCallbackLegacy = null;
111
     outputFileUri = null;
125
     outputFileUri = null;
112
   }
126
   }
113
 
127
 
116
 
130
 
117
   private Uri[] getSelectedFiles(Intent data, int resultCode) {
131
   private Uri[] getSelectedFiles(Intent data, int resultCode) {
118
     if (data == null) {
132
     if (data == null) {
119
-        return null;
133
+      return null;
120
     }
134
     }
121
 
135
 
122
     // we have one file selected
136
     // we have one file selected
123
     if (data.getData() != null) {
137
     if (data.getData() != null) {
124
-        if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
125
-            return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
126
-        } else {
127
-            return null;
128
-        }
138
+      if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
139
+        return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
140
+      } else {
141
+        return null;
142
+      }
129
     }
143
     }
130
 
144
 
131
     // we have multiple files selected
145
     // we have multiple files selected
132
     if (data.getClipData() != null) {
146
     if (data.getClipData() != null) {
133
-        final int numSelectedFiles = data.getClipData().getItemCount();
134
-        Uri[] result = new Uri[numSelectedFiles];
135
-        for (int i = 0; i < numSelectedFiles; i++) {
136
-            result[i] = data.getClipData().getItemAt(i).getUri();
137
-        }
138
-        return result;
147
+      final int numSelectedFiles = data.getClipData().getItemCount();
148
+      Uri[] result = new Uri[numSelectedFiles];
149
+      for (int i = 0; i < numSelectedFiles; i++) {
150
+        result[i] = data.getClipData().getItemAt(i).getUri();
151
+      }
152
+      return result;
139
     }
153
     }
140
     return null;
154
     return null;
141
   }
155
   }
142
 
156
 
143
   public void startPhotoPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType) {
157
   public void startPhotoPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType) {
144
-      filePathCallbackLegacy = filePathCallback;
158
+    filePathCallbackLegacy = filePathCallback;
145
 
159
 
146
-      Intent fileChooserIntent = getFileChooserIntent(acceptType);
147
-      Intent chooserIntent = Intent.createChooser(fileChooserIntent, "");
160
+    Intent fileChooserIntent = getFileChooserIntent(acceptType);
161
+    Intent chooserIntent = Intent.createChooser(fileChooserIntent, "");
148
 
162
 
149
-      ArrayList<Parcelable> extraIntents = new ArrayList<>();
150
-      if (acceptsImages(acceptType)) {
151
-          extraIntents.add(getPhotoIntent());
152
-      }
153
-      if (acceptsVideo(acceptType)) {
154
-          extraIntents.add(getVideoIntent());
155
-      }
156
-      chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
163
+    ArrayList<Parcelable> extraIntents = new ArrayList<>();
164
+    if (acceptsImages(acceptType)) {
165
+      extraIntents.add(getPhotoIntent());
166
+    }
167
+    if (acceptsVideo(acceptType)) {
168
+      extraIntents.add(getVideoIntent());
169
+    }
170
+    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
157
 
171
 
158
-      if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
159
-          getCurrentActivity().startActivityForResult(chooserIntent, PICKER_LEGACY);
160
-      } else {
161
-          Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
162
-      }
172
+    if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
173
+      getCurrentActivity().startActivityForResult(chooserIntent, PICKER_LEGACY);
174
+    } else {
175
+      Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
176
+    }
163
   }
177
   }
164
 
178
 
165
   @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
179
   @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
181
     chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
195
     chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
182
 
196
 
183
     if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
197
     if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
184
-        getCurrentActivity().startActivityForResult(chooserIntent, PICKER);
198
+      getCurrentActivity().startActivityForResult(chooserIntent, PICKER);
185
     } else {
199
     } else {
186
-        Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
200
+      Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
187
     }
201
     }
188
 
202
 
189
     return true;
203
     return true;
214
 
228
 
215
     if (!result) {
229
     if (!result) {
216
       PermissionAwareActivity activity = getPermissionAwareActivity();
230
       PermissionAwareActivity activity = getPermissionAwareActivity();
217
-      activity.requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }, FILE_DOWNLOAD_PERMISSION_REQUEST, webviewFileDownloaderPermissionListener);
231
+      activity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, FILE_DOWNLOAD_PERMISSION_REQUEST, webviewFileDownloaderPermissionListener);
218
     }
232
     }
219
 
233
 
220
     return result;
234
     return result;
270
   private Boolean acceptsImages(String types) {
284
   private Boolean acceptsImages(String types) {
271
     String mimeType = types;
285
     String mimeType = types;
272
     if (types.matches("\\.\\w+")) {
286
     if (types.matches("\\.\\w+")) {
273
-        mimeType = getMimeTypeFromExtension(types.replace(".", ""));
287
+      mimeType = getMimeTypeFromExtension(types.replace(".", ""));
274
     }
288
     }
275
     return mimeType.isEmpty() || mimeType.toLowerCase().contains("image");
289
     return mimeType.isEmpty() || mimeType.toLowerCase().contains("image");
276
   }
290
   }
291
+
277
   private Boolean acceptsImages(String[] types) {
292
   private Boolean acceptsImages(String[] types) {
278
     String[] mimeTypes = getAcceptedMimeType(types);
293
     String[] mimeTypes = getAcceptedMimeType(types);
279
     return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "image");
294
     return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "image");
282
   private Boolean acceptsVideo(String types) {
297
   private Boolean acceptsVideo(String types) {
283
     String mimeType = types;
298
     String mimeType = types;
284
     if (types.matches("\\.\\w+")) {
299
     if (types.matches("\\.\\w+")) {
285
-        mimeType = getMimeTypeFromExtension(types.replace(".", ""));
300
+      mimeType = getMimeTypeFromExtension(types.replace(".", ""));
286
     }
301
     }
287
     return mimeType.isEmpty() || mimeType.toLowerCase().contains("video");
302
     return mimeType.isEmpty() || mimeType.toLowerCase().contains("video");
288
   }
303
   }
304
+
289
   private Boolean acceptsVideo(String[] types) {
305
   private Boolean acceptsVideo(String[] types) {
290
     String[] mimeTypes = getAcceptedMimeType(types);
306
     String[] mimeTypes = getAcceptedMimeType(types);
291
     return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "video");
307
     return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "video");
292
   }
308
   }
293
 
309
 
294
-  private Boolean arrayContainsString(String[] array, String pattern){
295
-    for(String content : array){
296
-        if(content.contains(pattern)){
297
-            return true;
298
-        }
310
+  private Boolean arrayContainsString(String[] array, String pattern) {
311
+    for (String content : array) {
312
+      if (content.contains(pattern)) {
313
+        return true;
314
+      }
299
     }
315
     }
300
     return false;
316
     return false;
301
   }
317
   }
302
 
318
 
303
   private String[] getAcceptedMimeType(String[] types) {
319
   private String[] getAcceptedMimeType(String[] types) {
304
     if (isArrayEmpty(types)) {
320
     if (isArrayEmpty(types)) {
305
-        return new String[]{DEFAULT_MIME_TYPES};
321
+      return new String[]{DEFAULT_MIME_TYPES};
306
     }
322
     }
307
     String[] mimeTypes = new String[types.length];
323
     String[] mimeTypes = new String[types.length];
308
     for (int i = 0; i < types.length; i++) {
324
     for (int i = 0; i < types.length; i++) {
309
-        String t = types[i];
310
-        // convert file extensions to mime types
311
-        if (t.matches("\\.\\w+")) {
312
-            String mimeType = getMimeTypeFromExtension(t.replace(".", ""));
313
-            mimeTypes[i] = mimeType;
314
-        } else {
315
-            mimeTypes[i] = t;
316
-        }
325
+      String t = types[i];
326
+      // convert file extensions to mime types
327
+      if (t.matches("\\.\\w+")) {
328
+        String mimeType = getMimeTypeFromExtension(t.replace(".", ""));
329
+        mimeTypes[i] = mimeType;
330
+      } else {
331
+        mimeTypes[i] = t;
332
+      }
317
     }
333
     }
318
     return mimeTypes;
334
     return mimeTypes;
319
   }
335
   }
321
   private String getMimeTypeFromExtension(String extension) {
337
   private String getMimeTypeFromExtension(String extension) {
322
     String type = null;
338
     String type = null;
323
     if (extension != null) {
339
     if (extension != null) {
324
-        type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
340
+      type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
325
     }
341
     }
326
     return type;
342
     return type;
327
   }
343
   }
329
   private Uri getOutputUri(String intentType) {
345
   private Uri getOutputUri(String intentType) {
330
     File capturedFile = null;
346
     File capturedFile = null;
331
     try {
347
     try {
332
-        capturedFile = getCapturedFile(intentType);
348
+      capturedFile = getCapturedFile(intentType);
333
     } catch (IOException e) {
349
     } catch (IOException e) {
334
-        Log.e("CREATE FILE", "Error occurred while creating the File", e);
335
-        e.printStackTrace();
350
+      Log.e("CREATE FILE", "Error occurred while creating the File", e);
351
+      e.printStackTrace();
336
     }
352
     }
337
 
353
 
338
     // for versions below 6.0 (23) we use the old File creation & permissions model
354
     // for versions below 6.0 (23) we use the old File creation & permissions model
339
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
355
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
340
-        return Uri.fromFile(capturedFile);
356
+      return Uri.fromFile(capturedFile);
341
     }
357
     }
342
 
358
 
343
     // for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
359
     // for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
344
     String packageName = getReactApplicationContext().getPackageName();
360
     String packageName = getReactApplicationContext().getPackageName();
345
-    return FileProvider.getUriForFile(getReactApplicationContext(), packageName+".fileprovider", capturedFile);
361
+    return FileProvider.getUriForFile(getReactApplicationContext(), packageName + ".fileprovider", capturedFile);
346
   }
362
   }
347
 
363
 
348
   private File getCapturedFile(String intentType) throws IOException {
364
   private File getCapturedFile(String intentType) throws IOException {
365
 
381
 
366
     // for versions below 6.0 (23) we use the old File creation & permissions model
382
     // for versions below 6.0 (23) we use the old File creation & permissions model
367
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
383
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
368
-        // only this Directory works on all tested Android versions
369
-        // ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
370
-        File storageDir = Environment.getExternalStoragePublicDirectory(dir);
371
-        return new File(storageDir, filename);
384
+      // only this Directory works on all tested Android versions
385
+      // ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
386
+      File storageDir = Environment.getExternalStoragePublicDirectory(dir);
387
+      return new File(storageDir, filename);
372
     }
388
     }
373
 
389
 
374
     File storageDir = getReactApplicationContext().getExternalFilesDir(null);
390
     File storageDir = getReactApplicationContext().getExternalFilesDir(null);
385
   private PermissionAwareActivity getPermissionAwareActivity() {
401
   private PermissionAwareActivity getPermissionAwareActivity() {
386
     Activity activity = getCurrentActivity();
402
     Activity activity = getCurrentActivity();
387
     if (activity == null) {
403
     if (activity == null) {
388
-        throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
404
+      throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
389
     } else if (!(activity instanceof PermissionAwareActivity)) {
405
     } else if (!(activity instanceof PermissionAwareActivity)) {
390
-        throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
406
+      throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
391
     }
407
     }
392
     return (PermissionAwareActivity) activity;
408
     return (PermissionAwareActivity) activity;
393
   }
409
   }
394
-
395
-  private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
396
-    @Override
397
-    public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
398
-      switch (requestCode) {
399
-        case FILE_DOWNLOAD_PERMISSION_REQUEST: {
400
-          // If request is cancelled, the result arrays are empty.
401
-          if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
402
-            if (downloadRequest != null) {
403
-              downloadFile();
404
-            }
405
-          } else {
406
-            Toast.makeText(getCurrentActivity().getApplicationContext(), "Cannot download files as permission was denied. Please provide permission to write to storage, in order to download files.", Toast.LENGTH_LONG).show();
407
-          }
408
-          return true;
409
-        }
410
-      }
411
-      return false;
412
-    }
413
-  };
414
 }
410
 }

+ 33
- 34
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewPackage.java View File

1
-
2
 package com.reactnativecommunity.webview;
1
 package com.reactnativecommunity.webview;
3
 
2
 
4
-import java.util.ArrayList;
5
-import java.util.Arrays;
6
-import java.util.Collections;
7
-import java.util.List;
8
-
9
 import com.facebook.react.ReactPackage;
3
 import com.facebook.react.ReactPackage;
4
+import com.facebook.react.bridge.JavaScriptModule;
10
 import com.facebook.react.bridge.NativeModule;
5
 import com.facebook.react.bridge.NativeModule;
11
 import com.facebook.react.bridge.ReactApplicationContext;
6
 import com.facebook.react.bridge.ReactApplicationContext;
12
 import com.facebook.react.uimanager.ViewManager;
7
 import com.facebook.react.uimanager.ViewManager;
13
-import com.facebook.react.bridge.JavaScriptModule;
8
+
9
+import java.util.ArrayList;
10
+import java.util.Arrays;
11
+import java.util.Collections;
12
+import java.util.List;
14
 
13
 
15
 public class RNCWebViewPackage implements ReactPackage {
14
 public class RNCWebViewPackage implements ReactPackage {
16
 
15
 
17
-    private RNCWebViewManager manager;
18
-    private RNCWebViewModule module;
19
-
20
-    @Override
21
-    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
22
-      List<NativeModule> modulesList = new ArrayList<>();
23
-      module = new RNCWebViewModule(reactContext);
24
-      module.setPackage(this);
25
-      modulesList.add(module);
26
-      return modulesList;
27
-    }
28
-
29
-    // Deprecated from RN 0.47
30
-    public List<Class<? extends JavaScriptModule>> createJSModules() {
31
-      return Collections.emptyList();
32
-    }
33
-
34
-    @Override
35
-    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
36
-      manager = new RNCWebViewManager();
37
-      manager.setPackage(this);
38
-      return Arrays.<ViewManager>asList(manager);
39
-    }
40
-
41
-    public RNCWebViewModule getModule() {
42
-      return module;
43
-    }
16
+  private RNCWebViewManager manager;
17
+  private RNCWebViewModule module;
18
+
19
+  @Override
20
+  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
21
+    List<NativeModule> modulesList = new ArrayList<>();
22
+    module = new RNCWebViewModule(reactContext);
23
+    module.setPackage(this);
24
+    modulesList.add(module);
25
+    return modulesList;
26
+  }
27
+
28
+  // Deprecated from RN 0.47
29
+  public List<Class<? extends JavaScriptModule>> createJSModules() {
30
+    return Collections.emptyList();
31
+  }
32
+
33
+  @Override
34
+  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
35
+    manager = new RNCWebViewManager();
36
+    manager.setPackage(this);
37
+    return Arrays.<ViewManager>asList(manager);
38
+  }
39
+
40
+  public RNCWebViewModule getModule() {
41
+    return module;
42
+  }
44
 }
43
 }

+ 9
- 9
android/src/main/java/com/reactnativecommunity/webview/events/TopLoadingErrorEvent.kt View File

8
  * Event emitted when there is an error in loading.
8
  * Event emitted when there is an error in loading.
9
  */
9
  */
10
 class TopLoadingErrorEvent(viewId: Int, private val mEventData: WritableMap) :
10
 class TopLoadingErrorEvent(viewId: Int, private val mEventData: WritableMap) :
11
-    Event<TopLoadingErrorEvent>(viewId) {
12
-    companion object {
13
-        const val EVENT_NAME = "topLoadingError"
14
-    }
11
+  Event<TopLoadingErrorEvent>(viewId) {
12
+  companion object {
13
+    const val EVENT_NAME = "topLoadingError"
14
+  }
15
 
15
 
16
-    override fun getEventName(): String = EVENT_NAME
16
+  override fun getEventName(): String = EVENT_NAME
17
 
17
 
18
-    override fun canCoalesce(): Boolean = false
18
+  override fun canCoalesce(): Boolean = false
19
 
19
 
20
-    override fun getCoalescingKey(): Short = 0
20
+  override fun getCoalescingKey(): Short = 0
21
 
21
 
22
-    override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
-        rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
22
+  override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
+    rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
24
 
24
 
25
 }
25
 }

+ 9
- 9
android/src/main/java/com/reactnativecommunity/webview/events/TopLoadingFinishEvent.kt View File

8
  * Event emitted when loading is completed.
8
  * Event emitted when loading is completed.
9
  */
9
  */
10
 class TopLoadingFinishEvent(viewId: Int, private val mEventData: WritableMap) :
10
 class TopLoadingFinishEvent(viewId: Int, private val mEventData: WritableMap) :
11
-    Event<TopLoadingFinishEvent>(viewId) {
12
-    companion object {
13
-        const val EVENT_NAME = "topLoadingFinish"
14
-    }
11
+  Event<TopLoadingFinishEvent>(viewId) {
12
+  companion object {
13
+    const val EVENT_NAME = "topLoadingFinish"
14
+  }
15
 
15
 
16
-    override fun getEventName(): String = EVENT_NAME
16
+  override fun getEventName(): String = EVENT_NAME
17
 
17
 
18
-    override fun canCoalesce(): Boolean = false
18
+  override fun canCoalesce(): Boolean = false
19
 
19
 
20
-    override fun getCoalescingKey(): Short = 0
20
+  override fun getCoalescingKey(): Short = 0
21
 
21
 
22
-    override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
-        rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
22
+  override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
+    rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
24
 }
24
 }

+ 9
- 9
android/src/main/java/com/reactnativecommunity/webview/events/TopLoadingProgressEvent.kt View File

8
  * Event emitted when there is a loading progress event.
8
  * Event emitted when there is a loading progress event.
9
  */
9
  */
10
 class TopLoadingProgressEvent(viewId: Int, private val mEventData: WritableMap) :
10
 class TopLoadingProgressEvent(viewId: Int, private val mEventData: WritableMap) :
11
-    Event<TopLoadingProgressEvent>(viewId) {
12
-    companion object {
13
-        const val EVENT_NAME = "topLoadingProgress"
14
-    }
11
+  Event<TopLoadingProgressEvent>(viewId) {
12
+  companion object {
13
+    const val EVENT_NAME = "topLoadingProgress"
14
+  }
15
 
15
 
16
-    override fun getEventName(): String = EVENT_NAME
16
+  override fun getEventName(): String = EVENT_NAME
17
 
17
 
18
-    override fun canCoalesce(): Boolean = false
18
+  override fun canCoalesce(): Boolean = false
19
 
19
 
20
-    override fun getCoalescingKey(): Short = 0
20
+  override fun getCoalescingKey(): Short = 0
21
 
21
 
22
-    override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
-        rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
22
+  override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
+    rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
24
 }
24
 }

+ 9
- 9
android/src/main/java/com/reactnativecommunity/webview/events/TopLoadingStartEvent.kt View File

8
  * Event emitted when loading has started
8
  * Event emitted when loading has started
9
  */
9
  */
10
 class TopLoadingStartEvent(viewId: Int, private val mEventData: WritableMap) :
10
 class TopLoadingStartEvent(viewId: Int, private val mEventData: WritableMap) :
11
-    Event<TopLoadingStartEvent>(viewId) {
12
-    companion object {
13
-        const val EVENT_NAME = "topLoadingStart"
14
-    }
11
+  Event<TopLoadingStartEvent>(viewId) {
12
+  companion object {
13
+    const val EVENT_NAME = "topLoadingStart"
14
+  }
15
 
15
 
16
-    override fun getEventName(): String = EVENT_NAME
16
+  override fun getEventName(): String = EVENT_NAME
17
 
17
 
18
-    override fun canCoalesce(): Boolean = false
18
+  override fun canCoalesce(): Boolean = false
19
 
19
 
20
-    override fun getCoalescingKey(): Short = 0
20
+  override fun getCoalescingKey(): Short = 0
21
 
21
 
22
-    override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
-        rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
22
+  override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
+    rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
24
 
24
 
25
 }
25
 }

+ 11
- 11
android/src/main/java/com/reactnativecommunity/webview/events/TopMessageEvent.kt View File

8
  * Event emitted when there is an error in loading.
8
  * Event emitted when there is an error in loading.
9
  */
9
  */
10
 class TopMessageEvent(viewId: Int, private val mData: String) : Event<TopMessageEvent>(viewId) {
10
 class TopMessageEvent(viewId: Int, private val mData: String) : Event<TopMessageEvent>(viewId) {
11
-    companion object {
12
-        const val EVENT_NAME = "topMessage"
13
-    }
11
+  companion object {
12
+    const val EVENT_NAME = "topMessage"
13
+  }
14
 
14
 
15
-    override fun getEventName(): String = EVENT_NAME
15
+  override fun getEventName(): String = EVENT_NAME
16
 
16
 
17
-    override fun canCoalesce(): Boolean = false
17
+  override fun canCoalesce(): Boolean = false
18
 
18
 
19
-    override fun getCoalescingKey(): Short = 0
19
+  override fun getCoalescingKey(): Short = 0
20
 
20
 
21
-    override fun dispatch(rctEventEmitter: RCTEventEmitter) {
22
-        val data = Arguments.createMap()
23
-        data.putString("data", mData)
24
-        rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, data)
25
-    }
21
+  override fun dispatch(rctEventEmitter: RCTEventEmitter) {
22
+    val data = Arguments.createMap()
23
+    data.putString("data", mData)
24
+    rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, data)
25
+  }
26
 }
26
 }

+ 11
- 11
android/src/main/java/com/reactnativecommunity/webview/events/TopShouldStartLoadWithRequestEvent.kt View File

8
  * Event emitted when shouldOverrideUrlLoading is called
8
  * Event emitted when shouldOverrideUrlLoading is called
9
  */
9
  */
10
 class TopShouldStartLoadWithRequestEvent(viewId: Int, private val mData: WritableMap) : Event<TopShouldStartLoadWithRequestEvent>(viewId) {
10
 class TopShouldStartLoadWithRequestEvent(viewId: Int, private val mData: WritableMap) : Event<TopShouldStartLoadWithRequestEvent>(viewId) {
11
-    companion object {
12
-        const val EVENT_NAME = "topShouldStartLoadWithRequest"
13
-    }
11
+  companion object {
12
+    const val EVENT_NAME = "topShouldStartLoadWithRequest"
13
+  }
14
 
14
 
15
-    init {
16
-        mData.putString("navigationType", "other")
17
-    }
15
+  init {
16
+    mData.putString("navigationType", "other")
17
+  }
18
 
18
 
19
-    override fun getEventName(): String = EVENT_NAME
19
+  override fun getEventName(): String = EVENT_NAME
20
 
20
 
21
-    override fun canCoalesce(): Boolean = false
21
+  override fun canCoalesce(): Boolean = false
22
 
22
 
23
-    override fun getCoalescingKey(): Short = 0
23
+  override fun getCoalescingKey(): Short = 0
24
 
24
 
25
-    override fun dispatch(rctEventEmitter: RCTEventEmitter) =
26
-            rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, mData)
25
+  override fun dispatch(rctEventEmitter: RCTEventEmitter) =
26
+    rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, mData)
27
 }
27
 }

+ 4
- 2
android/src/main/res/xml/file_provider_paths.xml View File

1
 <?xml version="1.0" encoding="utf-8"?>
1
 <?xml version="1.0" encoding="utf-8"?>
2
-<paths xmlns:android="http://schemas.android.com/apk/res/android">
3
-    <external-path name="shared" path="." />
2
+<paths>
3
+  <external-path
4
+    name="shared"
5
+    path="." />
4
 </paths>
6
 </paths>