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. 🙂
This commit is contained in:
parent
0d3b1df2e3
commit
e697dff1d0
|
@ -0,0 +1,6 @@
|
||||||
|
[*]
|
||||||
|
charset=utf-8
|
||||||
|
end_of_line=lf
|
||||||
|
insert_final_newline=false
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
|
@ -1,4 +1,6 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.reactnativecommunity.webview">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.reactnativecommunity.webview">
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
<provider
|
<provider
|
||||||
android:name=".RNCWebViewFileProvider"
|
android:name=".RNCWebViewFileProvider"
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.support.v4.content.FileProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Providing a custom {@code FileProvider} prevents manifest {@code <provider>} name collisions.
|
* Providing a custom {@code FileProvider} prevents manifest {@code <provider>} name collisions.
|
||||||
*
|
* <p>
|
||||||
* See https://developer.android.com/guide/topics/manifest/provider-element.html for details.
|
* See https://developer.android.com/guide/topics/manifest/provider-element.html for details.
|
||||||
*/
|
*/
|
||||||
public class RNCWebViewFileProvider extends FileProvider {
|
public class RNCWebViewFileProvider extends FileProvider {
|
||||||
|
|
|
@ -4,24 +4,6 @@ import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.DownloadManager;
|
import android.app.DownloadManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.facebook.react.uimanager.UIManagerModule;
|
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -43,7 +25,6 @@ import android.webkit.WebSettings;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
import com.facebook.common.logging.FLog;
|
|
||||||
import com.facebook.react.bridge.Arguments;
|
import com.facebook.react.bridge.Arguments;
|
||||||
import com.facebook.react.bridge.LifecycleEventListener;
|
import com.facebook.react.bridge.LifecycleEventListener;
|
||||||
import com.facebook.react.bridge.ReactContext;
|
import com.facebook.react.bridge.ReactContext;
|
||||||
|
@ -52,47 +33,51 @@ import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.common.MapBuilder;
|
import com.facebook.react.common.MapBuilder;
|
||||||
import com.facebook.react.common.ReactConstants;
|
|
||||||
import com.facebook.react.common.build.ReactBuildConfig;
|
import com.facebook.react.common.build.ReactBuildConfig;
|
||||||
import com.facebook.react.module.annotations.ReactModule;
|
import com.facebook.react.module.annotations.ReactModule;
|
||||||
import com.facebook.react.uimanager.SimpleViewManager;
|
import com.facebook.react.uimanager.SimpleViewManager;
|
||||||
import com.facebook.react.uimanager.ThemedReactContext;
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
|
import com.facebook.react.uimanager.UIManagerModule;
|
||||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
|
import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
|
||||||
import com.facebook.react.uimanager.events.Event;
|
import com.facebook.react.uimanager.events.Event;
|
||||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
|
||||||
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
|
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
|
||||||
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
|
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
|
||||||
|
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
|
||||||
import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
|
import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
|
||||||
import com.reactnativecommunity.webview.events.TopMessageEvent;
|
import com.reactnativecommunity.webview.events.TopMessageEvent;
|
||||||
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
|
|
||||||
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
|
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages instances of {@link WebView}
|
* Manages instances of {@link WebView}
|
||||||
*
|
* <p>
|
||||||
* Can accept following commands:
|
* Can accept following commands:
|
||||||
* - GO_BACK
|
* - GO_BACK
|
||||||
* - GO_FORWARD
|
* - GO_FORWARD
|
||||||
* - RELOAD
|
* - RELOAD
|
||||||
* - LOAD_URL
|
* - LOAD_URL
|
||||||
*
|
* <p>
|
||||||
* {@link WebView} instances could emit following direct events:
|
* {@link WebView} instances could emit following direct events:
|
||||||
* - topLoadingFinish
|
* - topLoadingFinish
|
||||||
* - topLoadingStart
|
* - topLoadingStart
|
||||||
* - topLoadingStart
|
* - topLoadingStart
|
||||||
* - topLoadingProgress
|
* - topLoadingProgress
|
||||||
* - topShouldStartLoadWithRequest
|
* - topShouldStartLoadWithRequest
|
||||||
*
|
* <p>
|
||||||
* Each event will carry the following properties:
|
* Each event will carry the following properties:
|
||||||
* - target - view's react tag
|
* - target - view's react tag
|
||||||
* - url - url set for the webview
|
* - url - url set for the webview
|
||||||
|
@ -104,15 +89,6 @@ import org.json.JSONObject;
|
||||||
@ReactModule(name = RNCWebViewManager.REACT_CLASS)
|
@ReactModule(name = RNCWebViewManager.REACT_CLASS)
|
||||||
public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
|
|
||||||
protected static final String REACT_CLASS = "RNCWebView";
|
|
||||||
private RNCWebViewPackage aPackage;
|
|
||||||
|
|
||||||
protected static final String HTML_ENCODING = "UTF-8";
|
|
||||||
protected static final String HTML_MIME_TYPE = "text/html";
|
|
||||||
protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
|
|
||||||
|
|
||||||
protected static final String HTTP_METHOD_POST = "POST";
|
|
||||||
|
|
||||||
public static final int COMMAND_GO_BACK = 1;
|
public static final int COMMAND_GO_BACK = 1;
|
||||||
public static final int COMMAND_GO_FORWARD = 2;
|
public static final int COMMAND_GO_FORWARD = 2;
|
||||||
public static final int COMMAND_RELOAD = 3;
|
public static final int COMMAND_RELOAD = 3;
|
||||||
|
@ -120,246 +96,16 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
public static final int COMMAND_POST_MESSAGE = 5;
|
public static final int COMMAND_POST_MESSAGE = 5;
|
||||||
public static final int COMMAND_INJECT_JAVASCRIPT = 6;
|
public static final int COMMAND_INJECT_JAVASCRIPT = 6;
|
||||||
public static final int COMMAND_LOAD_URL = 7;
|
public static final int COMMAND_LOAD_URL = 7;
|
||||||
|
protected static final String REACT_CLASS = "RNCWebView";
|
||||||
|
protected static final String HTML_ENCODING = "UTF-8";
|
||||||
|
protected static final String HTML_MIME_TYPE = "text/html";
|
||||||
|
protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
|
||||||
|
protected static final String HTTP_METHOD_POST = "POST";
|
||||||
// Use `webView.loadUrl("about:blank")` to reliably reset the view
|
// Use `webView.loadUrl("about:blank")` to reliably reset the view
|
||||||
// state and release page resources (including any running JavaScript).
|
// state and release page resources (including any running JavaScript).
|
||||||
protected static final String BLANK_URL = "about:blank";
|
protected static final String BLANK_URL = "about:blank";
|
||||||
|
|
||||||
protected WebViewConfig mWebViewConfig;
|
protected WebViewConfig mWebViewConfig;
|
||||||
|
private RNCWebViewPackage aPackage;
|
||||||
protected static class RNCWebViewClient extends WebViewClient {
|
|
||||||
|
|
||||||
protected boolean mLastLoadFailed = false;
|
|
||||||
protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPageFinished(WebView webView, String url) {
|
|
||||||
super.onPageFinished(webView, url);
|
|
||||||
|
|
||||||
if (!mLastLoadFailed) {
|
|
||||||
RNCWebView reactWebView = (RNCWebView) webView;
|
|
||||||
|
|
||||||
reactWebView.callInjectedJavaScript();
|
|
||||||
|
|
||||||
emitFinishEvent(webView, url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
|
|
||||||
super.onPageStarted(webView, url, favicon);
|
|
||||||
mLastLoadFailed = false;
|
|
||||||
|
|
||||||
dispatchEvent(
|
|
||||||
webView,
|
|
||||||
new TopLoadingStartEvent(
|
|
||||||
webView.getId(),
|
|
||||||
createWebViewEvent(webView, url)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
|
||||||
dispatchEvent(
|
|
||||||
view,
|
|
||||||
new TopShouldStartLoadWithRequestEvent(
|
|
||||||
view.getId(),
|
|
||||||
createWebViewEvent(view, url)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.N)
|
|
||||||
@Override
|
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
|
||||||
final String url = request.getUrl().toString();
|
|
||||||
return this.shouldOverrideUrlLoading(view, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceivedError(
|
|
||||||
WebView webView,
|
|
||||||
int errorCode,
|
|
||||||
String description,
|
|
||||||
String failingUrl) {
|
|
||||||
super.onReceivedError(webView, errorCode, description, failingUrl);
|
|
||||||
mLastLoadFailed = true;
|
|
||||||
|
|
||||||
// In case of an error JS side expect to get a finish event first, and then get an error event
|
|
||||||
// Android WebView does it in the opposite way, so we need to simulate that behavior
|
|
||||||
emitFinishEvent(webView, failingUrl);
|
|
||||||
|
|
||||||
WritableMap eventData = createWebViewEvent(webView, failingUrl);
|
|
||||||
eventData.putDouble("code", errorCode);
|
|
||||||
eventData.putString("description", description);
|
|
||||||
|
|
||||||
dispatchEvent(
|
|
||||||
webView,
|
|
||||||
new TopLoadingErrorEvent(webView.getId(), eventData));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void emitFinishEvent(WebView webView, String url) {
|
|
||||||
dispatchEvent(
|
|
||||||
webView,
|
|
||||||
new TopLoadingFinishEvent(
|
|
||||||
webView.getId(),
|
|
||||||
createWebViewEvent(webView, url)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected WritableMap createWebViewEvent(WebView webView, String url) {
|
|
||||||
WritableMap event = Arguments.createMap();
|
|
||||||
event.putDouble("target", webView.getId());
|
|
||||||
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
|
|
||||||
// like onPageFinished
|
|
||||||
event.putString("url", url);
|
|
||||||
event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
|
|
||||||
event.putString("title", webView.getTitle());
|
|
||||||
event.putBoolean("canGoBack", webView.canGoBack());
|
|
||||||
event.putBoolean("canGoForward", webView.canGoForward());
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
|
|
||||||
mUrlPrefixesForDefaultIntent = specialUrls;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
|
|
||||||
* to call {@link WebView#destroy} on activity destroy event and also to clear the client
|
|
||||||
*/
|
|
||||||
protected static class RNCWebView extends WebView implements LifecycleEventListener {
|
|
||||||
protected @Nullable String injectedJS;
|
|
||||||
protected boolean messagingEnabled = false;
|
|
||||||
protected @Nullable RNCWebViewClient mRNCWebViewClient;
|
|
||||||
protected boolean sendContentSizeChangeEvents = false;
|
|
||||||
public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
|
|
||||||
this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected class RNCWebViewBridge {
|
|
||||||
RNCWebView mContext;
|
|
||||||
|
|
||||||
RNCWebViewBridge(RNCWebView c) {
|
|
||||||
mContext = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called whenever JavaScript running within the web view calls:
|
|
||||||
* - window[JAVASCRIPT_INTERFACE].postMessage
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
public void postMessage(String message) {
|
|
||||||
mContext.onMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebView must be created with an context of the current activity
|
|
||||||
*
|
|
||||||
* Activity Context is required for creation of dialogs internally by WebView
|
|
||||||
* Reactive Native needed for access to ReactNative internal system functionality
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public RNCWebView(ThemedReactContext reactContext) {
|
|
||||||
super(reactContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onHostResume() {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onHostPause() {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onHostDestroy() {
|
|
||||||
cleanupCallbacksAndDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSizeChanged(int w, int h, int ow, int oh) {
|
|
||||||
super.onSizeChanged(w, h, ow, oh);
|
|
||||||
|
|
||||||
if (sendContentSizeChangeEvents) {
|
|
||||||
dispatchEvent(
|
|
||||||
this,
|
|
||||||
new ContentSizeChangeEvent(
|
|
||||||
this.getId(),
|
|
||||||
w,
|
|
||||||
h
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWebViewClient(WebViewClient client) {
|
|
||||||
super.setWebViewClient(client);
|
|
||||||
mRNCWebViewClient = (RNCWebViewClient)client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable RNCWebViewClient getRNCWebViewClient() {
|
|
||||||
return mRNCWebViewClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInjectedJavaScript(@Nullable String js) {
|
|
||||||
injectedJS = js;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
|
|
||||||
return new RNCWebViewBridge(webView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("AddJavascriptInterface")
|
|
||||||
public void setMessagingEnabled(boolean enabled) {
|
|
||||||
if (messagingEnabled == enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
messagingEnabled = enabled;
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
|
|
||||||
} else {
|
|
||||||
removeJavascriptInterface(JAVASCRIPT_INTERFACE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void evaluateJavascriptWithFallback(String script) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
||||||
evaluateJavascript(script, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
// UTF-8 should always be supported
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callInjectedJavaScript() {
|
|
||||||
if (getSettings().getJavaScriptEnabled() &&
|
|
||||||
injectedJS != null &&
|
|
||||||
!TextUtils.isEmpty(injectedJS)) {
|
|
||||||
evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMessage(String message) {
|
|
||||||
dispatchEvent(this, new TopMessageEvent(this.getId(), message));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void cleanupCallbacksAndDestroy() {
|
|
||||||
setWebViewClient(null);
|
|
||||||
destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public RNCWebViewManager() {
|
public RNCWebViewManager() {
|
||||||
mWebViewConfig = new WebViewConfig() {
|
mWebViewConfig = new WebViewConfig() {
|
||||||
|
@ -372,6 +118,13 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
mWebViewConfig = webViewConfig;
|
mWebViewConfig = webViewConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void dispatchEvent(WebView webView, Event event) {
|
||||||
|
ReactContext reactContext = (ReactContext) webView.getContext();
|
||||||
|
EventDispatcher eventDispatcher =
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||||
|
eventDispatcher.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return REACT_CLASS;
|
return REACT_CLASS;
|
||||||
|
@ -404,7 +157,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
event.putString("title", webView.getTitle());
|
event.putString("title", webView.getTitle());
|
||||||
event.putBoolean("canGoBack", webView.canGoBack());
|
event.putBoolean("canGoBack", webView.canGoBack());
|
||||||
event.putBoolean("canGoForward", webView.canGoForward());
|
event.putBoolean("canGoForward", webView.canGoForward());
|
||||||
event.putDouble("progress", (float)newProgress/100);
|
event.putDouble("progress", (float) newProgress / 100);
|
||||||
dispatchEvent(
|
dispatchEvent(
|
||||||
webView,
|
webView,
|
||||||
new TopLoadingProgressEvent(
|
new TopLoadingProgressEvent(
|
||||||
|
@ -420,9 +173,11 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
|
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
|
||||||
getModule().startPhotoPickerIntent(filePathCallback, acceptType);
|
getModule().startPhotoPickerIntent(filePathCallback, acceptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
|
protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
|
||||||
getModule().startPhotoPickerIntent(filePathCallback, "");
|
getModule().startPhotoPickerIntent(filePathCallback, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
|
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
|
||||||
getModule().startPhotoPickerIntent(filePathCallback, acceptType);
|
getModule().startPhotoPickerIntent(filePathCallback, acceptType);
|
||||||
}
|
}
|
||||||
|
@ -729,7 +484,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Map<String, Integer> getCommandsMap() {
|
public @Nullable
|
||||||
|
Map<String, Integer> getCommandsMap() {
|
||||||
return MapBuilder.of(
|
return MapBuilder.of(
|
||||||
"goBack", COMMAND_GO_BACK,
|
"goBack", COMMAND_GO_BACK,
|
||||||
"goForward", COMMAND_GO_FORWARD,
|
"goForward", COMMAND_GO_FORWARD,
|
||||||
|
@ -796,13 +552,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
((RNCWebView) webView).cleanupCallbacksAndDestroy();
|
((RNCWebView) webView).cleanupCallbacksAndDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void dispatchEvent(WebView webView, Event event) {
|
|
||||||
ReactContext reactContext = (ReactContext) webView.getContext();
|
|
||||||
EventDispatcher eventDispatcher =
|
|
||||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
|
||||||
eventDispatcher.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RNCWebViewPackage getPackage() {
|
public RNCWebViewPackage getPackage() {
|
||||||
return this.aPackage;
|
return this.aPackage;
|
||||||
}
|
}
|
||||||
|
@ -814,4 +563,241 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
public RNCWebViewModule getModule() {
|
public RNCWebViewModule getModule() {
|
||||||
return this.aPackage.getModule();
|
return this.aPackage.getModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static class RNCWebViewClient extends WebViewClient {
|
||||||
|
|
||||||
|
protected boolean mLastLoadFailed = false;
|
||||||
|
protected @Nullable
|
||||||
|
ReadableArray mUrlPrefixesForDefaultIntent;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView webView, String url) {
|
||||||
|
super.onPageFinished(webView, url);
|
||||||
|
|
||||||
|
if (!mLastLoadFailed) {
|
||||||
|
RNCWebView reactWebView = (RNCWebView) webView;
|
||||||
|
|
||||||
|
reactWebView.callInjectedJavaScript();
|
||||||
|
|
||||||
|
emitFinishEvent(webView, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
|
||||||
|
super.onPageStarted(webView, url, favicon);
|
||||||
|
mLastLoadFailed = false;
|
||||||
|
|
||||||
|
dispatchEvent(
|
||||||
|
webView,
|
||||||
|
new TopLoadingStartEvent(
|
||||||
|
webView.getId(),
|
||||||
|
createWebViewEvent(webView, url)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
dispatchEvent(
|
||||||
|
view,
|
||||||
|
new TopShouldStartLoadWithRequestEvent(
|
||||||
|
view.getId(),
|
||||||
|
createWebViewEvent(view, url)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||||
|
final String url = request.getUrl().toString();
|
||||||
|
return this.shouldOverrideUrlLoading(view, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(
|
||||||
|
WebView webView,
|
||||||
|
int errorCode,
|
||||||
|
String description,
|
||||||
|
String failingUrl) {
|
||||||
|
super.onReceivedError(webView, errorCode, description, failingUrl);
|
||||||
|
mLastLoadFailed = true;
|
||||||
|
|
||||||
|
// In case of an error JS side expect to get a finish event first, and then get an error event
|
||||||
|
// Android WebView does it in the opposite way, so we need to simulate that behavior
|
||||||
|
emitFinishEvent(webView, failingUrl);
|
||||||
|
|
||||||
|
WritableMap eventData = createWebViewEvent(webView, failingUrl);
|
||||||
|
eventData.putDouble("code", errorCode);
|
||||||
|
eventData.putString("description", description);
|
||||||
|
|
||||||
|
dispatchEvent(
|
||||||
|
webView,
|
||||||
|
new TopLoadingErrorEvent(webView.getId(), eventData));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void emitFinishEvent(WebView webView, String url) {
|
||||||
|
dispatchEvent(
|
||||||
|
webView,
|
||||||
|
new TopLoadingFinishEvent(
|
||||||
|
webView.getId(),
|
||||||
|
createWebViewEvent(webView, url)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WritableMap createWebViewEvent(WebView webView, String url) {
|
||||||
|
WritableMap event = Arguments.createMap();
|
||||||
|
event.putDouble("target", webView.getId());
|
||||||
|
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
|
||||||
|
// like onPageFinished
|
||||||
|
event.putString("url", url);
|
||||||
|
event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
|
||||||
|
event.putString("title", webView.getTitle());
|
||||||
|
event.putBoolean("canGoBack", webView.canGoBack());
|
||||||
|
event.putBoolean("canGoForward", webView.canGoForward());
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
|
||||||
|
mUrlPrefixesForDefaultIntent = specialUrls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
|
||||||
|
* to call {@link WebView#destroy} on activity destroy event and also to clear the client
|
||||||
|
*/
|
||||||
|
protected static class RNCWebView extends WebView implements LifecycleEventListener {
|
||||||
|
protected @Nullable
|
||||||
|
String injectedJS;
|
||||||
|
protected boolean messagingEnabled = false;
|
||||||
|
protected @Nullable
|
||||||
|
RNCWebViewClient mRNCWebViewClient;
|
||||||
|
protected boolean sendContentSizeChangeEvents = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebView must be created with an context of the current activity
|
||||||
|
* <p>
|
||||||
|
* Activity Context is required for creation of dialogs internally by WebView
|
||||||
|
* Reactive Native needed for access to ReactNative internal system functionality
|
||||||
|
*/
|
||||||
|
public RNCWebView(ThemedReactContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
|
||||||
|
this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHostResume() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHostPause() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHostDestroy() {
|
||||||
|
cleanupCallbacksAndDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int ow, int oh) {
|
||||||
|
super.onSizeChanged(w, h, ow, oh);
|
||||||
|
|
||||||
|
if (sendContentSizeChangeEvents) {
|
||||||
|
dispatchEvent(
|
||||||
|
this,
|
||||||
|
new ContentSizeChangeEvent(
|
||||||
|
this.getId(),
|
||||||
|
w,
|
||||||
|
h
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWebViewClient(WebViewClient client) {
|
||||||
|
super.setWebViewClient(client);
|
||||||
|
mRNCWebViewClient = (RNCWebViewClient) client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable
|
||||||
|
RNCWebViewClient getRNCWebViewClient() {
|
||||||
|
return mRNCWebViewClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInjectedJavaScript(@Nullable String js) {
|
||||||
|
injectedJS = js;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
|
||||||
|
return new RNCWebViewBridge(webView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("AddJavascriptInterface")
|
||||||
|
public void setMessagingEnabled(boolean enabled) {
|
||||||
|
if (messagingEnabled == enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
messagingEnabled = enabled;
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
|
||||||
|
} else {
|
||||||
|
removeJavascriptInterface(JAVASCRIPT_INTERFACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void evaluateJavascriptWithFallback(String script) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
evaluateJavascript(script, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
// UTF-8 should always be supported
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void callInjectedJavaScript() {
|
||||||
|
if (getSettings().getJavaScriptEnabled() &&
|
||||||
|
injectedJS != null &&
|
||||||
|
!TextUtils.isEmpty(injectedJS)) {
|
||||||
|
evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(String message) {
|
||||||
|
dispatchEvent(this, new TopMessageEvent(this.getId(), message));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void cleanupCallbacksAndDestroy() {
|
||||||
|
setWebViewClient(null);
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class RNCWebViewBridge {
|
||||||
|
RNCWebView mContext;
|
||||||
|
|
||||||
|
RNCWebViewBridge(RNCWebView c) {
|
||||||
|
mContext = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called whenever JavaScript running within the web view calls:
|
||||||
|
* - window[JAVASCRIPT_INTERFACE].postMessage
|
||||||
|
*/
|
||||||
|
@JavascriptInterface
|
||||||
|
public void postMessage(String message) {
|
||||||
|
mContext.onMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package com.reactnativecommunity.webview;
|
package com.reactnativecommunity.webview;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
@ -37,20 +36,35 @@ import static android.app.Activity.RESULT_OK;
|
||||||
|
|
||||||
public class RNCWebViewModule extends ReactContextBaseJavaModule implements ActivityEventListener {
|
public class RNCWebViewModule extends ReactContextBaseJavaModule implements ActivityEventListener {
|
||||||
|
|
||||||
private final ReactApplicationContext reactContext;
|
|
||||||
private RNCWebViewPackage aPackage;
|
|
||||||
|
|
||||||
private static final int PICKER = 1;
|
private static final int PICKER = 1;
|
||||||
private static final int PICKER_LEGACY = 3;
|
private static final int PICKER_LEGACY = 3;
|
||||||
|
private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
|
||||||
|
final String DEFAULT_MIME_TYPES = "*/*";
|
||||||
|
private final ReactApplicationContext reactContext;
|
||||||
|
private RNCWebViewPackage aPackage;
|
||||||
private ValueCallback<Uri> filePathCallbackLegacy;
|
private ValueCallback<Uri> filePathCallbackLegacy;
|
||||||
private ValueCallback<Uri[]> filePathCallback;
|
private ValueCallback<Uri[]> filePathCallback;
|
||||||
private Uri outputFileUri;
|
private Uri outputFileUri;
|
||||||
|
|
||||||
private DownloadManager.Request downloadRequest;
|
private DownloadManager.Request downloadRequest;
|
||||||
private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
|
private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
|
||||||
|
@Override
|
||||||
final String DEFAULT_MIME_TYPES = "*/*";
|
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case FILE_DOWNLOAD_PERMISSION_REQUEST: {
|
||||||
|
// If request is cancelled, the result arrays are empty.
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
if (downloadRequest != null) {
|
||||||
|
downloadFile();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public RNCWebViewModule(ReactApplicationContext reactContext) {
|
public RNCWebViewModule(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
|
@ -96,7 +110,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
filePathCallback.onReceiveValue(result);
|
filePathCallback.onReceiveValue(result);
|
||||||
} else {
|
} else {
|
||||||
filePathCallback.onReceiveValue(new Uri[] { outputFileUri });
|
filePathCallback.onReceiveValue(new Uri[]{outputFileUri});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -107,7 +121,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||||
|
|
||||||
}
|
}
|
||||||
filePathCallback = null;
|
filePathCallback = null;
|
||||||
filePathCallbackLegacy= null;
|
filePathCallbackLegacy = null;
|
||||||
outputFileUri = null;
|
outputFileUri = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +228,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
PermissionAwareActivity activity = getPermissionAwareActivity();
|
PermissionAwareActivity activity = getPermissionAwareActivity();
|
||||||
activity.requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }, FILE_DOWNLOAD_PERMISSION_REQUEST, webviewFileDownloaderPermissionListener);
|
activity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, FILE_DOWNLOAD_PERMISSION_REQUEST, webviewFileDownloaderPermissionListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -274,6 +288,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||||
}
|
}
|
||||||
return mimeType.isEmpty() || mimeType.toLowerCase().contains("image");
|
return mimeType.isEmpty() || mimeType.toLowerCase().contains("image");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean acceptsImages(String[] types) {
|
private Boolean acceptsImages(String[] types) {
|
||||||
String[] mimeTypes = getAcceptedMimeType(types);
|
String[] mimeTypes = getAcceptedMimeType(types);
|
||||||
return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "image");
|
return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "image");
|
||||||
|
@ -286,14 +301,15 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||||
}
|
}
|
||||||
return mimeType.isEmpty() || mimeType.toLowerCase().contains("video");
|
return mimeType.isEmpty() || mimeType.toLowerCase().contains("video");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean acceptsVideo(String[] types) {
|
private Boolean acceptsVideo(String[] types) {
|
||||||
String[] mimeTypes = getAcceptedMimeType(types);
|
String[] mimeTypes = getAcceptedMimeType(types);
|
||||||
return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "video");
|
return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "video");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean arrayContainsString(String[] array, String pattern){
|
private Boolean arrayContainsString(String[] array, String pattern) {
|
||||||
for(String content : array){
|
for (String content : array) {
|
||||||
if(content.contains(pattern)){
|
if (content.contains(pattern)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,7 +358,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||||
|
|
||||||
// for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
|
// for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
|
||||||
String packageName = getReactApplicationContext().getPackageName();
|
String packageName = getReactApplicationContext().getPackageName();
|
||||||
return FileProvider.getUriForFile(getReactApplicationContext(), packageName+".fileprovider", capturedFile);
|
return FileProvider.getUriForFile(getReactApplicationContext(), packageName + ".fileprovider", capturedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getCapturedFile(String intentType) throws IOException {
|
private File getCapturedFile(String intentType) throws IOException {
|
||||||
|
@ -391,24 +407,4 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||||
}
|
}
|
||||||
return (PermissionAwareActivity) activity;
|
return (PermissionAwareActivity) activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
||||||
switch (requestCode) {
|
|
||||||
case FILE_DOWNLOAD_PERMISSION_REQUEST: {
|
|
||||||
// If request is cancelled, the result arrays are empty.
|
|
||||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
if (downloadRequest != null) {
|
|
||||||
downloadFile();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
|
|
||||||
package com.reactnativecommunity.webview;
|
package com.reactnativecommunity.webview;
|
||||||
|
|
||||||
|
import com.facebook.react.ReactPackage;
|
||||||
|
import com.facebook.react.bridge.JavaScriptModule;
|
||||||
|
import com.facebook.react.bridge.NativeModule;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.uimanager.ViewManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.facebook.react.ReactPackage;
|
|
||||||
import com.facebook.react.bridge.NativeModule;
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.uimanager.ViewManager;
|
|
||||||
import com.facebook.react.bridge.JavaScriptModule;
|
|
||||||
|
|
||||||
public class RNCWebViewPackage implements ReactPackage {
|
public class RNCWebViewPackage implements ReactPackage {
|
||||||
|
|
||||||
private RNCWebViewManager manager;
|
private RNCWebViewManager manager;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
<paths>
|
||||||
<external-path name="shared" path="." />
|
<external-path
|
||||||
|
name="shared"
|
||||||
|
path="." />
|
||||||
</paths>
|
</paths>
|
||||||
|
|
Loading…
Reference in New Issue