mirror of
https://github.com/status-im/react-native-webview.git
synced 2025-02-20 16:08:52 +00:00
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
6
android/.editorconfig
Normal file
6
android/.editorconfig
Normal file
@ -0,0 +1,6 @@
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
insert_final_newline=false
|
||||
indent_style=space
|
||||
indent_size=2
|
@ -1,13 +1,15 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.reactnativecommunity.webview">
|
||||
<application>
|
||||
<provider
|
||||
android:name=".RNCWebViewFileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.reactnativecommunity.webview">
|
||||
|
||||
<application>
|
||||
<provider
|
||||
android:name=".RNCWebViewFileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
|
@ -4,7 +4,7 @@ import android.support.v4.content.FileProvider;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public class RNCWebViewFileProvider extends FileProvider {
|
||||
|
@ -4,24 +4,6 @@ import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.DownloadManager;
|
||||
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.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
@ -43,7 +25,6 @@ import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
@ -52,67 +33,62 @@ import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.SimpleViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
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.TopLoadingFinishEvent;
|
||||
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
|
||||
import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
|
||||
import com.reactnativecommunity.webview.events.TopMessageEvent;
|
||||
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
|
||||
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.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}
|
||||
*
|
||||
* <p>
|
||||
* Can accept following commands:
|
||||
* - GO_BACK
|
||||
* - GO_FORWARD
|
||||
* - RELOAD
|
||||
* - LOAD_URL
|
||||
*
|
||||
* - GO_BACK
|
||||
* - GO_FORWARD
|
||||
* - RELOAD
|
||||
* - LOAD_URL
|
||||
* <p>
|
||||
* {@link WebView} instances could emit following direct events:
|
||||
* - topLoadingFinish
|
||||
* - topLoadingStart
|
||||
* - topLoadingStart
|
||||
* - topLoadingProgress
|
||||
* - topShouldStartLoadWithRequest
|
||||
*
|
||||
* - topLoadingFinish
|
||||
* - topLoadingStart
|
||||
* - topLoadingStart
|
||||
* - topLoadingProgress
|
||||
* - topShouldStartLoadWithRequest
|
||||
* <p>
|
||||
* Each event will carry the following properties:
|
||||
* - target - view's react tag
|
||||
* - url - url set for the webview
|
||||
* - loading - whether webview is in a loading state
|
||||
* - title - title of the current page
|
||||
* - canGoBack - boolean, whether there is anything on a history stack to go back
|
||||
* - canGoForward - boolean, whether it is possible to request GO_FORWARD command
|
||||
* - target - view's react tag
|
||||
* - url - url set for the webview
|
||||
* - loading - whether webview is in a loading state
|
||||
* - title - title of the current page
|
||||
* - canGoBack - boolean, whether there is anything on a history stack to go back
|
||||
* - canGoForward - boolean, whether it is possible to request GO_FORWARD command
|
||||
*/
|
||||
@ReactModule(name = RNCWebViewManager.REACT_CLASS)
|
||||
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_FORWARD = 2;
|
||||
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_INJECT_JAVASCRIPT = 6;
|
||||
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
|
||||
// state and release page resources (including any running JavaScript).
|
||||
protected static final String BLANK_URL = "about:blank";
|
||||
|
||||
protected WebViewConfig mWebViewConfig;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
private RNCWebViewPackage aPackage;
|
||||
|
||||
public RNCWebViewManager() {
|
||||
mWebViewConfig = new WebViewConfig() {
|
||||
@ -372,6 +118,13 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
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
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
@ -396,21 +149,21 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView webView, int newProgress) {
|
||||
@Override
|
||||
public void onProgressChanged(WebView webView, int newProgress) {
|
||||
super.onProgressChanged(webView, newProgress);
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble("target", webView.getId());
|
||||
event.putString("title", webView.getTitle());
|
||||
event.putBoolean("canGoBack", webView.canGoBack());
|
||||
event.putBoolean("canGoForward", webView.canGoForward());
|
||||
event.putDouble("progress", (float)newProgress/100);
|
||||
event.putDouble("progress", (float) newProgress / 100);
|
||||
dispatchEvent(
|
||||
webView,
|
||||
new TopLoadingProgressEvent(
|
||||
webView.getId(),
|
||||
event));
|
||||
}
|
||||
webView,
|
||||
new TopLoadingProgressEvent(
|
||||
webView.getId(),
|
||||
event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
|
||||
@ -420,9 +173,11 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
|
||||
getModule().startPhotoPickerIntent(filePathCallback, acceptType);
|
||||
}
|
||||
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
|
||||
getModule().startPhotoPickerIntent(filePathCallback, "");
|
||||
}
|
||||
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
|
||||
getModule().startPhotoPickerIntent(filePathCallback, acceptType);
|
||||
}
|
||||
@ -453,8 +208,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
|
||||
// Fixes broken full-screen modals/galleries due to body height being 0.
|
||||
webView.setLayoutParams(
|
||||
new LayoutParams(LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT));
|
||||
new LayoutParams(LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT));
|
||||
|
||||
setGeolocationEnabled(webView, false);
|
||||
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
@ -516,7 +271,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
public void setShowsVerticalScrollIndicator(WebView view, boolean enabled) {
|
||||
view.setVerticalScrollBarEnabled(enabled);
|
||||
}
|
||||
|
||||
|
||||
@ReactProp(name = "cacheEnabled")
|
||||
public void setCacheEnabled(WebView view, boolean enabled) {
|
||||
if (enabled) {
|
||||
@ -616,7 +371,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
String html = source.getString("html");
|
||||
if (source.hasKey("baseUrl")) {
|
||||
view.loadDataWithBaseURL(
|
||||
source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
|
||||
source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
|
||||
} else {
|
||||
view.loadData(html, HTML_MIME_TYPE + "; charset=" + HTML_ENCODING, null);
|
||||
}
|
||||
@ -689,8 +444,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
|
||||
@ReactProp(name = "urlPrefixesForDefaultIntent")
|
||||
public void setUrlPrefixesForDefaultIntent(
|
||||
WebView view,
|
||||
@Nullable ReadableArray urlPrefixesForDefaultIntent) {
|
||||
WebView view,
|
||||
@Nullable ReadableArray urlPrefixesForDefaultIntent) {
|
||||
RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
|
||||
if (client != null && urlPrefixesForDefaultIntent != null) {
|
||||
client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
|
||||
@ -729,16 +484,17 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<String, Integer> getCommandsMap() {
|
||||
public @Nullable
|
||||
Map<String, Integer> getCommandsMap() {
|
||||
return MapBuilder.of(
|
||||
"goBack", COMMAND_GO_BACK,
|
||||
"goForward", COMMAND_GO_FORWARD,
|
||||
"reload", COMMAND_RELOAD,
|
||||
"stopLoading", COMMAND_STOP_LOADING,
|
||||
"postMessage", COMMAND_POST_MESSAGE,
|
||||
"injectJavaScript", COMMAND_INJECT_JAVASCRIPT,
|
||||
"loadUrl", COMMAND_LOAD_URL
|
||||
);
|
||||
"goBack", COMMAND_GO_BACK,
|
||||
"goForward", COMMAND_GO_FORWARD,
|
||||
"reload", COMMAND_RELOAD,
|
||||
"stopLoading", COMMAND_STOP_LOADING,
|
||||
"postMessage", COMMAND_POST_MESSAGE,
|
||||
"injectJavaScript", COMMAND_INJECT_JAVASCRIPT,
|
||||
"loadUrl", COMMAND_LOAD_URL
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -765,13 +521,13 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
"var event;" +
|
||||
"var data = " + eventInitDict.toString() + ";" +
|
||||
"try {" +
|
||||
"event = new MessageEvent('message', data);" +
|
||||
"event = new MessageEvent('message', data);" +
|
||||
"} catch (e) {" +
|
||||
"event = document.createEvent('MessageEvent');" +
|
||||
"event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
|
||||
"event = document.createEvent('MessageEvent');" +
|
||||
"event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
|
||||
"}" +
|
||||
"document.dispatchEvent(event);" +
|
||||
"})();");
|
||||
"})();");
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -796,13 +552,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
((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() {
|
||||
return this.aPackage;
|
||||
}
|
||||
@ -814,4 +563,241 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||
public RNCWebViewModule 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;
|
||||
|
||||
import android.Manifest;
|
||||
@ -37,20 +36,35 @@ import static android.app.Activity.RESULT_OK;
|
||||
|
||||
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_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[]> filePathCallback;
|
||||
private Uri outputFileUri;
|
||||
|
||||
private DownloadManager.Request downloadRequest;
|
||||
private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
|
||||
|
||||
final String DEFAULT_MIME_TYPES = "*/*";
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
public RNCWebViewModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
@ -65,49 +79,49 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
|
||||
@ReactMethod
|
||||
public void isFileUploadSupported(final Promise promise) {
|
||||
Boolean result = false;
|
||||
int current = Build.VERSION.SDK_INT;
|
||||
if (current >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
result = true;
|
||||
}
|
||||
if (current >= Build.VERSION_CODES.JELLY_BEAN && current <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
result = true;
|
||||
}
|
||||
promise.resolve(result);
|
||||
Boolean result = false;
|
||||
int current = Build.VERSION.SDK_INT;
|
||||
if (current >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
result = true;
|
||||
}
|
||||
if (current >= Build.VERSION_CODES.JELLY_BEAN && current <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
result = true;
|
||||
}
|
||||
promise.resolve(result);
|
||||
}
|
||||
|
||||
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
|
||||
if (filePathCallback == null && filePathCallbackLegacy == null) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
// based off of which button was pressed, we get an activity result and a file
|
||||
// the camera activity doesn't properly return the filename* (I think?) so we use
|
||||
// this filename instead
|
||||
switch (requestCode) {
|
||||
case PICKER:
|
||||
case PICKER:
|
||||
if (resultCode != RESULT_OK) {
|
||||
if (filePathCallback != null) {
|
||||
filePathCallback.onReceiveValue(null);
|
||||
}
|
||||
if (filePathCallback != null) {
|
||||
filePathCallback.onReceiveValue(null);
|
||||
}
|
||||
} else {
|
||||
Uri result[] = this.getSelectedFiles(data, resultCode);
|
||||
if (result != null) {
|
||||
filePathCallback.onReceiveValue(result);
|
||||
} else {
|
||||
filePathCallback.onReceiveValue(new Uri[] { outputFileUri });
|
||||
}
|
||||
Uri result[] = this.getSelectedFiles(data, resultCode);
|
||||
if (result != null) {
|
||||
filePathCallback.onReceiveValue(result);
|
||||
} else {
|
||||
filePathCallback.onReceiveValue(new Uri[]{outputFileUri});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PICKER_LEGACY:
|
||||
case PICKER_LEGACY:
|
||||
Uri result = resultCode != Activity.RESULT_OK ? null : data == null ? outputFileUri : data.getData();
|
||||
filePathCallbackLegacy.onReceiveValue(result);
|
||||
break;
|
||||
|
||||
}
|
||||
filePathCallback = null;
|
||||
filePathCallbackLegacy= null;
|
||||
filePathCallbackLegacy = null;
|
||||
outputFileUri = null;
|
||||
}
|
||||
|
||||
@ -116,50 +130,50 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
|
||||
private Uri[] getSelectedFiles(Intent data, int resultCode) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
// we have one file selected
|
||||
if (data.getData() != null) {
|
||||
if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// we have multiple files selected
|
||||
if (data.getClipData() != null) {
|
||||
final int numSelectedFiles = data.getClipData().getItemCount();
|
||||
Uri[] result = new Uri[numSelectedFiles];
|
||||
for (int i = 0; i < numSelectedFiles; i++) {
|
||||
result[i] = data.getClipData().getItemAt(i).getUri();
|
||||
}
|
||||
return result;
|
||||
final int numSelectedFiles = data.getClipData().getItemCount();
|
||||
Uri[] result = new Uri[numSelectedFiles];
|
||||
for (int i = 0; i < numSelectedFiles; i++) {
|
||||
result[i] = data.getClipData().getItemAt(i).getUri();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void startPhotoPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType) {
|
||||
filePathCallbackLegacy = filePathCallback;
|
||||
filePathCallbackLegacy = filePathCallback;
|
||||
|
||||
Intent fileChooserIntent = getFileChooserIntent(acceptType);
|
||||
Intent chooserIntent = Intent.createChooser(fileChooserIntent, "");
|
||||
Intent fileChooserIntent = getFileChooserIntent(acceptType);
|
||||
Intent chooserIntent = Intent.createChooser(fileChooserIntent, "");
|
||||
|
||||
ArrayList<Parcelable> extraIntents = new ArrayList<>();
|
||||
if (acceptsImages(acceptType)) {
|
||||
extraIntents.add(getPhotoIntent());
|
||||
}
|
||||
if (acceptsVideo(acceptType)) {
|
||||
extraIntents.add(getVideoIntent());
|
||||
}
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
|
||||
ArrayList<Parcelable> extraIntents = new ArrayList<>();
|
||||
if (acceptsImages(acceptType)) {
|
||||
extraIntents.add(getPhotoIntent());
|
||||
}
|
||||
if (acceptsVideo(acceptType)) {
|
||||
extraIntents.add(getVideoIntent());
|
||||
}
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
|
||||
|
||||
if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
|
||||
getCurrentActivity().startActivityForResult(chooserIntent, PICKER_LEGACY);
|
||||
} else {
|
||||
Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
|
||||
}
|
||||
if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
|
||||
getCurrentActivity().startActivityForResult(chooserIntent, PICKER_LEGACY);
|
||||
} else {
|
||||
Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@ -181,9 +195,9 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
|
||||
|
||||
if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
|
||||
getCurrentActivity().startActivityForResult(chooserIntent, PICKER);
|
||||
getCurrentActivity().startActivityForResult(chooserIntent, PICKER);
|
||||
} else {
|
||||
Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
|
||||
Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -214,7 +228,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
|
||||
if (!result) {
|
||||
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;
|
||||
@ -270,10 +284,11 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
private Boolean acceptsImages(String types) {
|
||||
String mimeType = types;
|
||||
if (types.matches("\\.\\w+")) {
|
||||
mimeType = getMimeTypeFromExtension(types.replace(".", ""));
|
||||
mimeType = getMimeTypeFromExtension(types.replace(".", ""));
|
||||
}
|
||||
return mimeType.isEmpty() || mimeType.toLowerCase().contains("image");
|
||||
}
|
||||
|
||||
private Boolean acceptsImages(String[] types) {
|
||||
String[] mimeTypes = getAcceptedMimeType(types);
|
||||
return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "image");
|
||||
@ -282,38 +297,39 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
private Boolean acceptsVideo(String types) {
|
||||
String mimeType = types;
|
||||
if (types.matches("\\.\\w+")) {
|
||||
mimeType = getMimeTypeFromExtension(types.replace(".", ""));
|
||||
mimeType = getMimeTypeFromExtension(types.replace(".", ""));
|
||||
}
|
||||
return mimeType.isEmpty() || mimeType.toLowerCase().contains("video");
|
||||
}
|
||||
|
||||
private Boolean acceptsVideo(String[] types) {
|
||||
String[] mimeTypes = getAcceptedMimeType(types);
|
||||
return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "video");
|
||||
}
|
||||
|
||||
private Boolean arrayContainsString(String[] array, String pattern){
|
||||
for(String content : array){
|
||||
if(content.contains(pattern)){
|
||||
return true;
|
||||
}
|
||||
private Boolean arrayContainsString(String[] array, String pattern) {
|
||||
for (String content : array) {
|
||||
if (content.contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String[] getAcceptedMimeType(String[] types) {
|
||||
if (isArrayEmpty(types)) {
|
||||
return new String[]{DEFAULT_MIME_TYPES};
|
||||
return new String[]{DEFAULT_MIME_TYPES};
|
||||
}
|
||||
String[] mimeTypes = new String[types.length];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
String t = types[i];
|
||||
// convert file extensions to mime types
|
||||
if (t.matches("\\.\\w+")) {
|
||||
String mimeType = getMimeTypeFromExtension(t.replace(".", ""));
|
||||
mimeTypes[i] = mimeType;
|
||||
} else {
|
||||
mimeTypes[i] = t;
|
||||
}
|
||||
String t = types[i];
|
||||
// convert file extensions to mime types
|
||||
if (t.matches("\\.\\w+")) {
|
||||
String mimeType = getMimeTypeFromExtension(t.replace(".", ""));
|
||||
mimeTypes[i] = mimeType;
|
||||
} else {
|
||||
mimeTypes[i] = t;
|
||||
}
|
||||
}
|
||||
return mimeTypes;
|
||||
}
|
||||
@ -321,7 +337,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
private String getMimeTypeFromExtension(String extension) {
|
||||
String type = null;
|
||||
if (extension != null) {
|
||||
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
@ -329,20 +345,20 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
private Uri getOutputUri(String intentType) {
|
||||
File capturedFile = null;
|
||||
try {
|
||||
capturedFile = getCapturedFile(intentType);
|
||||
capturedFile = getCapturedFile(intentType);
|
||||
} catch (IOException e) {
|
||||
Log.e("CREATE FILE", "Error occurred while creating the File", e);
|
||||
e.printStackTrace();
|
||||
Log.e("CREATE FILE", "Error occurred while creating the File", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// for versions below 6.0 (23) we use the old File creation & permissions model
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return Uri.fromFile(capturedFile);
|
||||
return Uri.fromFile(capturedFile);
|
||||
}
|
||||
|
||||
// for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
|
||||
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 {
|
||||
@ -365,10 +381,10 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
|
||||
// for versions below 6.0 (23) we use the old File creation & permissions model
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
// only this Directory works on all tested Android versions
|
||||
// ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
|
||||
File storageDir = Environment.getExternalStoragePublicDirectory(dir);
|
||||
return new File(storageDir, filename);
|
||||
// only this Directory works on all tested Android versions
|
||||
// ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
|
||||
File storageDir = Environment.getExternalStoragePublicDirectory(dir);
|
||||
return new File(storageDir, filename);
|
||||
}
|
||||
|
||||
File storageDir = getReactApplicationContext().getExternalFilesDir(null);
|
||||
@ -385,30 +401,10 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
||||
private PermissionAwareActivity getPermissionAwareActivity() {
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
|
||||
throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
|
||||
} else if (!(activity instanceof PermissionAwareActivity)) {
|
||||
throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
|
||||
throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
|
||||
}
|
||||
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,44 +1,43 @@
|
||||
|
||||
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.Arrays;
|
||||
import java.util.Collections;
|
||||
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 {
|
||||
|
||||
private RNCWebViewManager manager;
|
||||
private RNCWebViewModule module;
|
||||
private RNCWebViewManager manager;
|
||||
private RNCWebViewModule module;
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modulesList = new ArrayList<>();
|
||||
module = new RNCWebViewModule(reactContext);
|
||||
module.setPackage(this);
|
||||
modulesList.add(module);
|
||||
return modulesList;
|
||||
}
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modulesList = new ArrayList<>();
|
||||
module = new RNCWebViewModule(reactContext);
|
||||
module.setPackage(this);
|
||||
modulesList.add(module);
|
||||
return modulesList;
|
||||
}
|
||||
|
||||
// Deprecated from RN 0.47
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// Deprecated from RN 0.47
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
manager = new RNCWebViewManager();
|
||||
manager.setPackage(this);
|
||||
return Arrays.<ViewManager>asList(manager);
|
||||
}
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
manager = new RNCWebViewManager();
|
||||
manager.setPackage(this);
|
||||
return Arrays.<ViewManager>asList(manager);
|
||||
}
|
||||
|
||||
public RNCWebViewModule getModule() {
|
||||
return module;
|
||||
}
|
||||
public RNCWebViewModule getModule() {
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
@ -8,18 +8,18 @@ import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
* Event emitted when there is an error in loading.
|
||||
*/
|
||||
class TopLoadingErrorEvent(viewId: Int, private val mEventData: WritableMap) :
|
||||
Event<TopLoadingErrorEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topLoadingError"
|
||||
}
|
||||
Event<TopLoadingErrorEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topLoadingError"
|
||||
}
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun canCoalesce(): Boolean = false
|
||||
override fun canCoalesce(): Boolean = false
|
||||
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
|
||||
|
||||
}
|
||||
|
@ -8,17 +8,17 @@ import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
* Event emitted when loading is completed.
|
||||
*/
|
||||
class TopLoadingFinishEvent(viewId: Int, private val mEventData: WritableMap) :
|
||||
Event<TopLoadingFinishEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topLoadingFinish"
|
||||
}
|
||||
Event<TopLoadingFinishEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topLoadingFinish"
|
||||
}
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun canCoalesce(): Boolean = false
|
||||
override fun canCoalesce(): Boolean = false
|
||||
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
|
||||
}
|
||||
|
@ -8,17 +8,17 @@ import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
* Event emitted when there is a loading progress event.
|
||||
*/
|
||||
class TopLoadingProgressEvent(viewId: Int, private val mEventData: WritableMap) :
|
||||
Event<TopLoadingProgressEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topLoadingProgress"
|
||||
}
|
||||
Event<TopLoadingProgressEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topLoadingProgress"
|
||||
}
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun canCoalesce(): Boolean = false
|
||||
override fun canCoalesce(): Boolean = false
|
||||
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
|
||||
}
|
||||
|
@ -8,18 +8,18 @@ import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
* Event emitted when loading has started
|
||||
*/
|
||||
class TopLoadingStartEvent(viewId: Int, private val mEventData: WritableMap) :
|
||||
Event<TopLoadingStartEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topLoadingStart"
|
||||
}
|
||||
Event<TopLoadingStartEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topLoadingStart"
|
||||
}
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun canCoalesce(): Boolean = false
|
||||
override fun canCoalesce(): Boolean = false
|
||||
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
|
||||
|
||||
}
|
||||
|
@ -8,19 +8,19 @@ import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
* Event emitted when there is an error in loading.
|
||||
*/
|
||||
class TopMessageEvent(viewId: Int, private val mData: String) : Event<TopMessageEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topMessage"
|
||||
}
|
||||
companion object {
|
||||
const val EVENT_NAME = "topMessage"
|
||||
}
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun canCoalesce(): Boolean = false
|
||||
override fun canCoalesce(): Boolean = false
|
||||
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
||||
val data = Arguments.createMap()
|
||||
data.putString("data", mData)
|
||||
rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, data)
|
||||
}
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
||||
val data = Arguments.createMap()
|
||||
data.putString("data", mData)
|
||||
rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, data)
|
||||
}
|
||||
}
|
||||
|
@ -8,20 +8,20 @@ import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
* Event emitted when shouldOverrideUrlLoading is called
|
||||
*/
|
||||
class TopShouldStartLoadWithRequestEvent(viewId: Int, private val mData: WritableMap) : Event<TopShouldStartLoadWithRequestEvent>(viewId) {
|
||||
companion object {
|
||||
const val EVENT_NAME = "topShouldStartLoadWithRequest"
|
||||
}
|
||||
companion object {
|
||||
const val EVENT_NAME = "topShouldStartLoadWithRequest"
|
||||
}
|
||||
|
||||
init {
|
||||
mData.putString("navigationType", "other")
|
||||
}
|
||||
init {
|
||||
mData.putString("navigationType", "other")
|
||||
}
|
||||
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
override fun getEventName(): String = EVENT_NAME
|
||||
|
||||
override fun canCoalesce(): Boolean = false
|
||||
override fun canCoalesce(): Boolean = false
|
||||
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
override fun getCoalescingKey(): Short = 0
|
||||
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, mData)
|
||||
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
|
||||
rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, mData)
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="shared" path="." />
|
||||
<paths>
|
||||
<external-path
|
||||
name="shared"
|
||||
path="." />
|
||||
</paths>
|
||||
|
Loading…
x
Reference in New Issue
Block a user