Commit ab0a5cc5 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

Added , (available only for Android), Added , and events to manage...

Added ,  (available only for Android), Added ,  and  events to manage javascript popup dialogs only for Android now, added support for javascript dialogs popups for iOS
parent bc6bed18
This diff is collapsed.
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
- Added new Android WebView options: `allowContentAccess`, `allowFileAccess`, `allowFileAccessFromFileURLs`, `allowUniversalAccessFromFileURLs`, `appCacheEnabled`, `appCachePath`, `blockNetworkImage`, `blockNetworkLoads`, `cacheMode`, `cursiveFontFamily`, `defaultFixedFontSize`, `defaultFontSize`, `defaultTextEncodingName`, `disabledActionModeMenuItems`, `fantasyFontFamily`, `fixedFontFamily`, `forceDark`, `geolocationEnabled`, `layoutAlgorithm`, `loadWithOverviewMode`, `loadsImagesAutomatically`, `minimumLogicalFontSize`, `needInitialFocus`, `offscreenPreRaster`, `sansSerifFontFamily`, `serifFontFamily`, `standardFontFamily` - Added new Android WebView options: `allowContentAccess`, `allowFileAccess`, `allowFileAccessFromFileURLs`, `allowUniversalAccessFromFileURLs`, `appCacheEnabled`, `appCachePath`, `blockNetworkImage`, `blockNetworkLoads`, `cacheMode`, `cursiveFontFamily`, `defaultFixedFontSize`, `defaultFontSize`, `defaultTextEncodingName`, `disabledActionModeMenuItems`, `fantasyFontFamily`, `fixedFontFamily`, `forceDark`, `geolocationEnabled`, `layoutAlgorithm`, `loadWithOverviewMode`, `loadsImagesAutomatically`, `minimumLogicalFontSize`, `needInitialFocus`, `offscreenPreRaster`, `sansSerifFontFamily`, `serifFontFamily`, `standardFontFamily`
- Added new iOS WebView options: `applicationNameForUserAgent`, `isFraudulentWebsiteWarningEnabled`, `selectionGranularity`, `dataDetectorTypes`, `preferredContentMode` - Added new iOS WebView options: `applicationNameForUserAgent`, `isFraudulentWebsiteWarningEnabled`, `selectionGranularity`, `dataDetectorTypes`, `preferredContentMode`
- Added `onGeolocationPermissionsShowPrompt` event and `GeolocationPermissionShowPromptResponse` class (available only for Android) - Added `onGeolocationPermissionsShowPrompt` event and `GeolocationPermissionShowPromptResponse` class (available only for Android)
- Added `startSafeBrowsing`, `setSafeBrowsingWhitelist` and `getSafeBrowsingPrivacyPolicyUrl` methods (available only for Android)
- Added `onJsAlert`, `onJsConfirm` and `onJsPrompt` events to manage javascript popup dialogs
### BREAKING CHANGES ### BREAKING CHANGES
- Deleted `WebResourceRequest` class - Deleted `WebResourceRequest` class
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
package="com.pichillilorenzo.flutter_inappbrowser"> package="com.pichillilorenzo.flutter_inappbrowser">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application> <application>
<activity android:theme="@style/AppTheme" android:name=".InAppBrowserActivity" android:configChanges="orientation|screenSize"></activity> <activity android:theme="@style/AppTheme" android:name=".InAppBrowserActivity" android:configChanges="orientation|screenSize"></activity>
......
...@@ -192,12 +192,11 @@ public class ContentBlockerHandler { ...@@ -192,12 +192,11 @@ public class ContentBlockerHandler {
return checkUrl(webView, url, responseResourceType); return checkUrl(webView, url, responseResourceType);
} }
public ContentBlockerTriggerResourceType getResourceTypeFromUrl(InAppWebView webView, String url) { public ContentBlockerTriggerResourceType getResourceTypeFromUrl(InAppWebView webView, String url) {
ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW; ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW;
// make an HTTP "HEAD" request to the server for that URL. This will not return the full content of the URL.
if (url.startsWith("http://") || url.startsWith("https://")) { if (url.startsWith("http://") || url.startsWith("https://")) {
// make an HTTP "HEAD" request to the server for that URL. This will not return the full content of the URL.
Request mRequest = new Request.Builder().url(url).head().build(); Request mRequest = new Request.Builder().url(url).head().build();
Response response = null; Response response = null;
try { try {
......
...@@ -8,6 +8,7 @@ public enum ContentBlockerTriggerResourceType { ...@@ -8,6 +8,7 @@ public enum ContentBlockerTriggerResourceType {
FONT ("font"), FONT ("font"),
SVG_DOCUMENT ("svg-document"), SVG_DOCUMENT ("svg-document"),
MEDIA ("media"), MEDIA ("media"),
POPUP ("popup"),
RAW ("raw"); RAW ("raw");
private final String value; private final String value;
......
...@@ -195,7 +195,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { ...@@ -195,7 +195,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
if (webView != null) if (webView != null)
webView.takeScreenshot(result); webView.takeScreenshot(result);
else else
result.error(LOG_TAG, "webView is null", null); result.success(null);
break; break;
case "setOptions": case "setOptions":
if (webView != null) { if (webView != null) {
...@@ -212,6 +212,20 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { ...@@ -212,6 +212,20 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
case "getCopyBackForwardList": case "getCopyBackForwardList":
result.success((webView != null) ? webView.getCopyBackForwardList() : null); result.success((webView != null) ? webView.getCopyBackForwardList() : null);
break; break;
case "startSafeBrowsing":
if (webView != null)
webView.startSafeBrowsing(result);
else
result.success(false);
break;
case "setSafeBrowsingWhitelist":
if (webView != null) {
List<String> hosts = (List<String>) call.argument("hosts");
webView.setSafeBrowsingWhitelist(hosts, result);
}
else
result.success(false);
break;
case "dispose": case "dispose":
dispose(); dispose();
result.success(true); result.success(true);
......
...@@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream; ...@@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import io.flutter.app.FlutterActivity; import io.flutter.app.FlutterActivity;
...@@ -460,4 +461,17 @@ public class InAppBrowserActivity extends AppCompatActivity { ...@@ -460,4 +461,17 @@ public class InAppBrowserActivity extends AppCompatActivity {
return null; return null;
} }
public void startSafeBrowsing(MethodChannel.Result result) {
if (webView != null)
webView.startSafeBrowsing(result);
else
result.success(false);
}
public void setSafeBrowsingWhitelist(List<String> hosts, MethodChannel.Result result) {
if (webView != null)
webView.setSafeBrowsingWhitelist(hosts, result);
else
result.success(false);
}
} }
...@@ -299,6 +299,12 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { ...@@ -299,6 +299,12 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
case "getCopyBackForwardList": case "getCopyBackForwardList":
result.success(getCopyBackForwardList(uuid)); result.success(getCopyBackForwardList(uuid));
break; break;
case "startSafeBrowsing":
startSafeBrowsing(uuid, result);
break;
case "setSafeBrowsingWhitelist":
setSafeBrowsingWhitelist(uuid, (List<String>) call.argument("hosts"), result);
break;
default: default:
result.notImplemented(); result.notImplemented();
} }
...@@ -668,4 +674,17 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { ...@@ -668,4 +674,17 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
return null; return null;
} }
public void startSafeBrowsing(String uuid, Result result) {
InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid);
if (inAppBrowserActivity != null)
inAppBrowserActivity.startSafeBrowsing(result);
result.success(false);
}
public void setSafeBrowsingWhitelist(String uuid, List<String> hosts, Result result) {
InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid);
if (inAppBrowserActivity != null)
inAppBrowserActivity.setSafeBrowsingWhitelist(hosts, result);
result.success(false);
}
} }
...@@ -12,6 +12,7 @@ import android.util.JsonToken; ...@@ -12,6 +12,7 @@ import android.util.JsonToken;
import android.util.Log; import android.util.Log;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.DownloadListener; import android.webkit.DownloadListener;
import android.webkit.JsResult;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList; import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem; import android.webkit.WebHistoryItem;
...@@ -53,6 +54,7 @@ public class InAppWebView extends WebView { ...@@ -53,6 +54,7 @@ public class InAppWebView extends WebView {
public InAppWebViewOptions options; public InAppWebViewOptions options;
public boolean isLoading = false; public boolean isLoading = false;
public OkHttpClient httpClient; public OkHttpClient httpClient;
public float scale = getResources().getDisplayMetrics().density;
int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB
public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler(); public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler();
...@@ -123,7 +125,7 @@ public class InAppWebView extends WebView { ...@@ -123,7 +125,7 @@ public class InAppWebView extends WebView {
public void prepare() { public void prepare() {
final Activity activity = (inAppBrowserActivity != null) ? inAppBrowserActivity : registrar.activity().getParent(); final Activity activity = (inAppBrowserActivity != null) ? inAppBrowserActivity : registrar.activity();
boolean isFromInAppBrowserActivity = inAppBrowserActivity != null; boolean isFromInAppBrowserActivity = inAppBrowserActivity != null;
...@@ -322,7 +324,6 @@ public class InAppWebView extends WebView { ...@@ -322,7 +324,6 @@ public class InAppWebView extends WebView {
post(new Runnable() { post(new Runnable() {
@Override @Override
public void run() { public void run() {
float scale = getResources().getDisplayMetrics().density; // getScale();
int height = (int) (getContentHeight() * scale + 0.5); int height = (int) (getContentHeight() * scale + 0.5);
Bitmap b = Bitmap.createBitmap( getWidth(), Bitmap b = Bitmap.createBitmap( getWidth(),
...@@ -644,7 +645,6 @@ public class InAppWebView extends WebView { ...@@ -644,7 +645,6 @@ public class InAppWebView extends WebView {
int oldt) { int oldt) {
super.onScrollChanged(l, t, oldl, oldt); super.onScrollChanged(l, t, oldl, oldt);
float scale = getResources().getDisplayMetrics().density;
int x = (int) (l/scale); int x = (int) (l/scale);
int y = (int) (t/scale); int y = (int) (t/scale);
...@@ -660,6 +660,33 @@ public class InAppWebView extends WebView { ...@@ -660,6 +660,33 @@ public class InAppWebView extends WebView {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel; return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel;
} }
public void startSafeBrowsing(final MethodChannel.Result result) {
Activity activity = (inAppBrowserActivity != null) ? inAppBrowserActivity : registrar.activity();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
startSafeBrowsing(activity.getApplicationContext(), new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean value) {
result.success(value);
}
});
} else {
result.success(false);
}
}
public void setSafeBrowsingWhitelist(List<String> hosts, final MethodChannel.Result result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setSafeBrowsingWhitelist(hosts, new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean value) {
result.success(value);
}
});
} else {
result.success(false);
}
}
class DownloadStartListener implements DownloadListener { class DownloadStartListener implements DownloadListener {
@Override @Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
......
...@@ -19,7 +19,6 @@ import android.webkit.WebViewClient; ...@@ -19,7 +19,6 @@ import android.webkit.WebViewClient;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappbrowser.ContentBlocker.ContentBlocker;
import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView; import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin;
...@@ -27,8 +26,6 @@ import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface; ...@@ -27,8 +26,6 @@ import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappbrowser.Util; import com.pichillilorenzo.flutter_inappbrowser.Util;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -303,6 +300,12 @@ public class InAppWebViewClient extends WebViewClient { ...@@ -303,6 +300,12 @@ public class InAppWebViewClient extends WebViewClient {
super.onReceivedHttpAuthRequest(view, handler, host, realm); super.onReceivedHttpAuthRequest(view, handler, host, realm);
} }
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
final InAppWebView webView = (InAppWebView) view;
webView.scale = newScale;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override @Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="InAppWebViewTheme" parent="Theme.AppCompat">
</style>
<style name="AppTheme" parent="Theme.AppCompat.Light"> <style name="AppTheme" parent="Theme.AppCompat.Light">
</style> </style>
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pichillilorenzo.flutter_inappbrowserexample"> package="com.pichillilorenzo.flutterwebviewexample">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application flutter needs it to communicate with the running application
...@@ -21,14 +21,14 @@ ...@@ -21,14 +21,14 @@
additional functionality it is fine to subclass or reimplement additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<application <application
android:name="com.pichillilorenzo.flutterwebviewexample.MyApplication" android:name=".MyApplication"
android:label="flutter_inappbrowser_example" android:label="flutter_inappbrowser_example"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/InAppWebViewTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
......
package com.pichillilorenzo.flutter_inappbrowserexample; package com.pichillilorenzo.flutterwebviewexample;
import android.os.Bundle; import android.os.Bundle;
import io.flutter.app.FlutterActivity; import io.flutter.app.FlutterActivity;
......
...@@ -55,8 +55,13 @@ ...@@ -55,8 +55,13 @@
}); });
}); });
$(document).ready(function() { $(document).ready(function() {
console.log("jQuery ready"); alert("Alert Popup");
console.log(confirm("Press a button!"));
console.log(prompt("Please enter your name", "Harry Potter"));
console.log("jQuery ready");
/*
if ("geolocation" in navigator) { if ("geolocation" in navigator) {
console.log("Geolocation API enabled"); console.log("Geolocation API enabled");
navigator.geolocation.getCurrentPosition(function(position) { navigator.geolocation.getCurrentPosition(function(position) {
...@@ -64,7 +69,7 @@ ...@@ -64,7 +69,7 @@
}); });
} else { } else {
console.log("No geolocation API"); console.log("No geolocation API");
} }*/
}); });
</script> </script>
</body> </body>
......
...@@ -32,6 +32,8 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -32,6 +32,8 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
String url = ""; String url = "";
double progress = 0; double progress = 0;
TextEditingController _textFieldController = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -171,18 +173,18 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -171,18 +173,18 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: new Text("Permission Geolocation API"), title: Text("Permission Geolocation API"),
content: new Text("Can we use Geolocation API?"), content: Text("Can we use Geolocation API?"),
actions: <Widget>[ actions: <Widget>[
new FlatButton( FlatButton(
child: new Text("Close"), child: Text("Close"),
onPressed: () { onPressed: () {
response = new GeolocationPermissionShowPromptResponse(origin, false, false); response = new GeolocationPermissionShowPromptResponse(origin, false, false);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
new FlatButton( FlatButton(
child: new Text("Accept"), child: Text("Accept"),
onPressed: () { onPressed: () {
response = new GeolocationPermissionShowPromptResponse(origin, true, true); response = new GeolocationPermissionShowPromptResponse(origin, true, true);
Navigator.of(context).pop(); Navigator.of(context).pop();
...@@ -194,7 +196,94 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -194,7 +196,94 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
); );
return response; return response;
} },
onJsAlert: (InAppWebViewController controller, String message) async {
JsAlertResponseAction action;
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () {
action = JsAlertResponseAction.CONFIRM;
Navigator.of(context).pop();
},
),
],
);
},
);
return new JsAlertResponse(handledByClient: true, action: action);
},
onJsConfirm: (InAppWebViewController controller, String message) async {
JsConfirmResponseAction action;
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Cancel"),
onPressed: () {
action = JsConfirmResponseAction.CANCEL;
Navigator.of(context).pop();
},
),
FlatButton(
child: Text("Ok"),
onPressed: () {
action = JsConfirmResponseAction.CONFIRM;
Navigator.of(context).pop();
},
),
],
);
},
);
return new JsConfirmResponse(handledByClient: true, action: action);
},
onJsPrompt: (InAppWebViewController controller, String message, String defaultValue) async {
JsPromptResponseAction action;
_textFieldController.text = defaultValue;
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(message),
content: TextField(
controller: _textFieldController,
),
actions: <Widget>[
FlatButton(
child: Text("Cancel"),
onPressed: () {
action = JsPromptResponseAction.CANCEL;
Navigator.of(context).pop();
},
),
FlatButton(
child: Text("Ok"),
onPressed: () {
action = JsPromptResponseAction.CONFIRM;
Navigator.of(context).pop();
},
),
],
);
},
);
return new JsPromptResponse(handledByClient: true, action: action, value: _textFieldController.text);
},
), ),
), ),
), ),
......
...@@ -44,6 +44,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { ...@@ -44,6 +44,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView {
do { do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) let blockRules = String(data: jsonData, encoding: String.Encoding.utf8)
print(blockRules)
WKContentRuleListStore.default().compileContentRuleList( WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "ContentBlockingRules", forIdentifier: "ContentBlockingRules",
encodedContentRuleList: blockRules) { (contentRuleList, error) in encodedContentRuleList: blockRules) { (contentRuleList, error) in
......
...@@ -745,6 +745,63 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -745,6 +745,63 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
} }
} }
public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = UIAlertController(title: message, message: nil,
preferredStyle: UIAlertController.Style.alert);
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default) {
_ in completionHandler()}
);
let presentingViewController = ((IABController != nil) ? IABController! : window!.rootViewController!)
presentingViewController.present(alertController, animated: true, completion: {})
}
public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (Bool) -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
completionHandler(false)
}))
let presentingViewController = ((IABController != nil) ? IABController! : window!.rootViewController!)
presentingViewController.present(alertController, animated: true, completion: nil)
}
public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
completionHandler(nil)
}))
let presentingViewController = ((IABController != nil) ? IABController! : window!.rootViewController!)
presentingViewController.present(alertController, animated: true, completion: nil)
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) { public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if navigationDelegate != nil { if navigationDelegate != nil {
let x = Int(scrollView.contentOffset.x / scrollView.contentScaleFactor) let x = Int(scrollView.contentOffset.x / scrollView.contentScaleFactor)
......
...@@ -327,39 +327,72 @@ class InAppBrowser { ...@@ -327,39 +327,72 @@ class InAppBrowser {
} }
///Event fires when the [InAppBrowser] webview scrolls. ///Event fires when the [InAppBrowser] webview scrolls.
///
///[x] represents the current horizontal scroll origin in pixels. ///[x] represents the current horizontal scroll origin in pixels.
///
///[y] represents the current vertical scroll origin in pixels. ///[y] represents the current vertical scroll origin in pixels.
void onScrollChanged(int x, int y) { void onScrollChanged(int x, int y) {
} }
///Event fires when [InAppBrowser] recognizes and starts a downloadable file. ///Event fires when [InAppBrowser] recognizes and starts a downloadable file.
///
///[url] represents the url of the file. ///[url] represents the url of the file.
void onDownloadStart(String url) { void onDownloadStart(String url) {
} }
///Event fires when the [InAppBrowser] webview finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a [CustomSchemeResponse] to load a specific resource encoded to `base64`. ///Event fires when the [InAppBrowser] webview finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a [CustomSchemeResponse] to load a specific resource encoded to `base64`.
///
///[scheme] represents the scheme of the url. ///[scheme] represents the scheme of the url.
///
///[url] represents the url of the request. ///[url] represents the url of the request.
Future<CustomSchemeResponse> onLoadResourceCustomScheme(String scheme, String url) { Future<CustomSchemeResponse> onLoadResourceCustomScheme(String scheme, String url) {
} }
///Event fires when the [InAppBrowser] webview tries to open a link with `target="_blank"`. ///Event fires when the [InAppBrowser] webview tries to open a link with `target="_blank"`.
///
///[url] represents the url of the link. ///[url] represents the url of the link.
void onTargetBlank(String url) { void onTargetBlank(String url) {
} }
///Event fires when javascript calls the `alert()` method to display an alert dialog.
///If [JsAlertResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
Future<JsAlertResponse> onJsAlert(String message) {
}
///Event fires when javascript calls the `confirm()` method to display a confirm dialog.
///If [JsConfirmResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
Future<JsConfirmResponse> onJsConfirm(String message) {
}
///Event fires when javascript calls the `prompt()` method to display a prompt dialog.
///If [JsPromptResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
///[defaultValue] represents the default value displayed in the prompt dialog.
Future<JsPromptResponse> onJsPrompt(String message, String defaultValue) {
}
///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin. ///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin.
///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https. ///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https.
///On non-secure origins geolocation requests are automatically denied. ///On non-secure origins geolocation requests are automatically denied.
///
///[origin] represents the origin of the web content attempting to use the Geolocation API. ///[origin] represents the origin of the web content attempting to use the Geolocation API.
///
///**NOTE**: available only for Android. ///**NOTE**: available only for Android.
Future<GeolocationPermissionShowPromptResponse> onGeolocationPermissionsShowPrompt (String origin) { Future<GeolocationPermissionShowPromptResponse> onGeolocationPermissionsShowPrompt (String origin) {
} }
void throwIsAlreadyOpened({String message = ''}) { void throwIsAlreadyOpened({String message = ''}) {
if (this.isOpened()) { if (this.isOpened()) {
...@@ -372,4 +405,5 @@ class InAppBrowser { ...@@ -372,4 +405,5 @@ class InAppBrowser {
throw Exception(['Error: ${ (message.isEmpty) ? '' : message + ' '}The browser is not opened.']); throw Exception(['Error: ${ (message.isEmpty) ? '' : message + ' '}The browser is not opened.']);
} }
} }
} }
...@@ -15,6 +15,9 @@ import 'in_app_browser.dart'; ...@@ -15,6 +15,9 @@ import 'in_app_browser.dart';
import 'channel_manager.dart'; import 'channel_manager.dart';
import 'webview_options.dart'; import 'webview_options.dart';
/*
* TODO: injectFileFromAssets, injectJavaScriptBeforeLoad
*/
///Initial [data] as a content for an [InAppWebView] instance, using [baseUrl] as the base URL for it. ///Initial [data] as a content for an [InAppWebView] instance, using [baseUrl] as the base URL for it.
///The [mimeType] property specifies the format of the data. ///The [mimeType] property specifies the format of the data.
...@@ -107,34 +110,61 @@ class InAppWebView extends StatefulWidget { ...@@ -107,34 +110,61 @@ class InAppWebView extends StatefulWidget {
/// ///
///**NOTE**: In order to be able to listen this event, you need to set `useOnLoadResource` option to `true`. ///**NOTE**: In order to be able to listen this event, you need to set `useOnLoadResource` option to `true`.
/// ///
///**NOTE only for iOS**: In some cases, the [response.data] of a [response] with `text/assets` encoding could be empty. ///**NOTE only for Android**: to be able to listen this event, you need also the enable javascript.
final onWebViewLoadResourceCallback onLoadResource; final onWebViewLoadResourceCallback onLoadResource;
///Event fires when the [InAppWebView] scrolls. ///Event fires when the [InAppWebView] scrolls.
///
///[x] represents the current horizontal scroll origin in pixels. ///[x] represents the current horizontal scroll origin in pixels.
///
///[y] represents the current vertical scroll origin in pixels. ///[y] represents the current vertical scroll origin in pixels.
final onWebViewScrollChangedCallback onScrollChanged; final onWebViewScrollChangedCallback onScrollChanged;
///Event fires when [InAppWebView] recognizes and starts a downloadable file. ///Event fires when [InAppWebView] recognizes and starts a downloadable file.
///
///[url] represents the url of the file. ///[url] represents the url of the file.
final onDownloadStartCallback onDownloadStart; final onDownloadStartCallback onDownloadStart;
///Event fires when the [InAppWebView] finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a [CustomSchemeResponse] to load a specific resource encoded to `base64`. ///Event fires when the [InAppWebView] finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a [CustomSchemeResponse] to load a specific resource encoded to `base64`.
///
///[scheme] represents the scheme of the url. ///[scheme] represents the scheme of the url.
///
///[url] represents the url of the request. ///[url] represents the url of the request.
final onLoadResourceCustomSchemeCallback onLoadResourceCustomScheme; final onLoadResourceCustomSchemeCallback onLoadResourceCustomScheme;
///Event fires when the [InAppWebView] tries to open a link with `target="_blank"`. ///Event fires when the [InAppWebView] tries to open a link with `target="_blank"`.
///
///[url] represents the url of the link. ///[url] represents the url of the link.
final onTargetBlankCallback onTargetBlank; final onTargetBlankCallback onTargetBlank;
///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin. ///Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin.
///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https. ///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https.
///On non-secure origins geolocation requests are automatically denied. ///On non-secure origins geolocation requests are automatically denied.
///
///[origin] represents the origin of the web content attempting to use the Geolocation API. ///[origin] represents the origin of the web content attempting to use the Geolocation API.
///
///**NOTE**: available only for Android. ///**NOTE**: available only for Android.
final onGeolocationPermissionsShowPromptCallback onGeolocationPermissionsShowPrompt; final onGeolocationPermissionsShowPromptCallback onGeolocationPermissionsShowPrompt;
///Event fires when javascript calls the `alert()` method to display an alert dialog.
///If [JsAlertResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
final onJsAlertCallback onJsAlert;
///Event fires when javascript calls the `confirm()` method to display a confirm dialog.
///If [JsConfirmResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
final onJsConfirmCallback onJsConfirm;
///Event fires when javascript calls the `prompt()` method to display a prompt dialog.
///If [JsPromptResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
///[defaultValue] represents the default value displayed in the prompt dialog.
final onJsPromptCallback onJsPrompt;
///Initial url that will be loaded. ///Initial url that will be loaded.
final String initialUrl; final String initialUrl;
///Initial asset file that will be loaded. See [InAppWebView.loadFile()] for explanation. ///Initial asset file that will be loaded. See [InAppWebView.loadFile()] for explanation.
...@@ -174,6 +204,9 @@ class InAppWebView extends StatefulWidget { ...@@ -174,6 +204,9 @@ class InAppWebView extends StatefulWidget {
this.onLoadResourceCustomScheme, this.onLoadResourceCustomScheme,
this.onTargetBlank, this.onTargetBlank,
this.onGeolocationPermissionsShowPrompt, this.onGeolocationPermissionsShowPrompt,
this.onJsAlert,
this.onJsConfirm,
this.onJsPrompt,
this.gestureRecognizers, this.gestureRecognizers,
}) : super(key: key); }) : super(key: key);
...@@ -269,16 +302,16 @@ class InAppWebViewController { ...@@ -269,16 +302,16 @@ class InAppWebViewController {
InAppWebViewController(int id, InAppWebView widget) { InAppWebViewController(int id, InAppWebView widget) {
_id = id; this._id = id;
_channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); this._channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id');
_channel.setMethodCallHandler(handleMethod); this._channel.setMethodCallHandler(handleMethod);
_widget = widget; this._widget = widget;
} }
InAppWebViewController.fromInAppBrowser(String uuid, MethodChannel channel, InAppBrowser inAppBrowser) { InAppWebViewController.fromInAppBrowser(String uuid, MethodChannel channel, InAppBrowser inAppBrowser) {
_inAppBrowserUuid = uuid; this._inAppBrowserUuid = uuid;
_channel = channel; this._channel = channel;
_inAppBrowser = inAppBrowser; this._inAppBrowser = inAppBrowser;
} }
Future<dynamic> handleMethod(MethodCall call) async { Future<dynamic> handleMethod(MethodCall call) async {
...@@ -399,6 +432,28 @@ class InAppWebViewController { ...@@ -399,6 +432,28 @@ class InAppWebViewController {
else if (_inAppBrowser != null) else if (_inAppBrowser != null)
return (await _inAppBrowser.onGeolocationPermissionsShowPrompt(origin)).toMap(); return (await _inAppBrowser.onGeolocationPermissionsShowPrompt(origin)).toMap();
break; break;
case "onJsAlert":
String message = call.arguments["message"];
if (_widget != null && _widget.onJsAlert != null)
return (await _widget.onJsAlert(this, message)).toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsAlert(message)).toMap();
break;
case "onJsConfirm":
String message = call.arguments["message"];
if (_widget != null && _widget.onJsConfirm != null)
return (await _widget.onJsConfirm(this, message)).toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsConfirm(message)).toMap();
break;
case "onJsPrompt":
String message = call.arguments["message"];
String defaultValue = call.arguments["defaultValue"];
if (_widget != null && _widget.onJsPrompt != null)
return (await _widget.onJsPrompt(this, message, defaultValue)).toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsPrompt(message, defaultValue)).toMap();
break;
case "onCallJsHandler": case "onCallJsHandler":
String handlerName = call.arguments["handlerName"]; String handlerName = call.arguments["handlerName"];
// decode args to json // decode args to json
...@@ -818,6 +873,52 @@ class InAppWebViewController { ...@@ -818,6 +873,52 @@ class InAppWebViewController {
} }
return WebHistory(historyList, currentIndex); return WebHistory(historyList, currentIndex);
} }
///Starts Safe Browsing initialization.
///
///URL loads are not guaranteed to be protected by Safe Browsing until after the this method returns true.
///Safe Browsing is not fully supported on all devices. For those devices this method will returns false.
///
///This should not be called if Safe Browsing has been disabled by manifest tag
///or [AndroidInAppWebViewOptions.safeBrowsingEnabled]. This prepares resources used for Safe Browsing.
///
///**NOTE**: available only for Android.
Future<bool> startSafeBrowsing() async {
Map<String, dynamic> args = <String, dynamic>{};
if (_inAppBrowserUuid != null && _inAppBrowser != null) {
_inAppBrowser.throwIsNotOpened();
args.putIfAbsent('uuid', () => _inAppBrowserUuid);
}
return await _channel.invokeMethod('startSafeBrowsing', args);
}
///Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks. The list is global for all the WebViews.
///
/// Each rule should take one of these:
///| Rule | Example | Matches Subdomain |
///| -- | -- | -- |
///| HOSTNAME | example.com | Yes |
///| .HOSTNAME | .example.com | No |
///| IPV4_LITERAL | 192.168.1.1 | No |
///| IPV6_LITERAL_WITH_BRACKETS | [10:20:30:40:50:60:70:80] | No |
///
///All other rules, including wildcards, are invalid. The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2).
///
///[hosts] represents the list of hosts. This value must never be null.
///
///**NOTE**: available only for Android.
Future<bool> setSafeBrowsingWhitelist(List<String> hosts) async {
assert(hosts != null);
Map<String, dynamic> args = <String, dynamic>{};
if (_inAppBrowserUuid != null && _inAppBrowser != null) {
_inAppBrowser.throwIsNotOpened();
args.putIfAbsent('uuid', () => _inAppBrowserUuid);
}
args.putIfAbsent('hosts', () => hosts);
return await _channel.invokeMethod('setSafeBrowsingWhitelist', args);
}
///Dispose/Destroy the WebView. ///Dispose/Destroy the WebView.
Future<void> _dispose() async { Future<void> _dispose() async {
await _channel.invokeMethod('dispose'); await _channel.invokeMethod('dispose');
......
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'in_app_webview.dart' show InAppWebViewController; import 'in_app_webview.dart';
var uuidGenerator = new Uuid(); var uuidGenerator = new Uuid();
...@@ -128,6 +128,95 @@ class GeolocationPermissionShowPromptResponse { ...@@ -128,6 +128,95 @@ class GeolocationPermissionShowPromptResponse {
} }
} }
class JsAlertResponseAction {
final int _value;
const JsAlertResponseAction._internal(this._value);
toValue() => _value;
static const CONFIRM = const JsAlertResponseAction._internal(0);
}
class JsAlertResponse {
String message;
String confirmButtonTitle;
bool handledByClient;
JsAlertResponseAction action;
JsAlertResponse({this.message = "", this.handledByClient = false, this.confirmButtonTitle = "", this.action = JsAlertResponseAction.CONFIRM});
Map<String, dynamic> toMap() {
return {
"message": message,
"confirmButtonTitle": confirmButtonTitle,
"handledByClient": handledByClient,
"action": action?.toValue()
};
}
}
class JsConfirmResponseAction {
final int _value;
const JsConfirmResponseAction._internal(this._value);
toValue() => _value;
static const CONFIRM = const JsConfirmResponseAction._internal(0);
static const CANCEL = const JsConfirmResponseAction._internal(1);
}
class JsConfirmResponse {
String message;
String confirmButtonTitle;
String cancelButtonTitle;
bool handledByClient;
JsConfirmResponseAction action;
JsConfirmResponse({this.message = "", this.handledByClient = false, this.confirmButtonTitle = "", this.cancelButtonTitle = "", this.action = JsConfirmResponseAction.CANCEL});
Map<String, dynamic> toMap() {
return {
"message": message,
"confirmButtonTitle": confirmButtonTitle,
"cancelButtonTitle": cancelButtonTitle,
"handledByClient": handledByClient,
"action": action?.toValue()
};
}
}
class JsPromptResponseAction {
final int _value;
const JsPromptResponseAction._internal(this._value);
toValue() => _value;
static const CONFIRM = const JsPromptResponseAction._internal(0);
static const CANCEL = const JsPromptResponseAction._internal(1);
}
class JsPromptResponse {
String message;
String defaultValue;
String confirmButtonTitle;
String cancelButtonTitle;
bool handledByClient;
String value;
JsPromptResponseAction action;
JsPromptResponse({this.message = "", this.defaultValue = "", this.handledByClient = false, this.confirmButtonTitle = "", this.cancelButtonTitle = "", this.value, this.action = JsPromptResponseAction.CANCEL});
Map<String, dynamic> toMap() {
return {
"message": message,
"defaultValue": defaultValue,
"confirmButtonTitle": confirmButtonTitle,
"cancelButtonTitle": cancelButtonTitle,
"handledByClient": handledByClient,
"value": value,
"action": action?.toValue()
};
}
}
typedef onWebViewCreatedCallback = void Function(InAppWebViewController controller); typedef onWebViewCreatedCallback = void Function(InAppWebViewController controller);
typedef onWebViewLoadStartCallback = void Function(InAppWebViewController controller, String url); typedef onWebViewLoadStartCallback = void Function(InAppWebViewController controller, String url);
typedef onWebViewLoadStopCallback = void Function(InAppWebViewController controller, String url); typedef onWebViewLoadStopCallback = void Function(InAppWebViewController controller, String url);
...@@ -141,3 +230,6 @@ typedef onDownloadStartCallback = void Function(InAppWebViewController controlle ...@@ -141,3 +230,6 @@ typedef onDownloadStartCallback = void Function(InAppWebViewController controlle
typedef onLoadResourceCustomSchemeCallback = Future<CustomSchemeResponse> Function(InAppWebViewController controller, String scheme, String url); typedef onLoadResourceCustomSchemeCallback = Future<CustomSchemeResponse> Function(InAppWebViewController controller, String scheme, String url);
typedef onTargetBlankCallback = void Function(InAppWebViewController controller, String url); typedef onTargetBlankCallback = void Function(InAppWebViewController controller, String url);
typedef onGeolocationPermissionsShowPromptCallback = Future<GeolocationPermissionShowPromptResponse> Function(InAppWebViewController controller, String origin); typedef onGeolocationPermissionsShowPromptCallback = Future<GeolocationPermissionShowPromptResponse> Function(InAppWebViewController controller, String origin);
typedef onJsAlertCallback = Future<JsAlertResponse> Function(InAppWebViewController controller, String message);
typedef onJsConfirmCallback = Future<JsConfirmResponse> Function(InAppWebViewController controller, String message);
typedef onJsPromptCallback = Future<JsPromptResponse> Function(InAppWebViewController controller, String message, String defaultValue);
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment