diff --git a/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java b/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java index e19b2a08..5c0b8c85 100644 --- a/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java +++ b/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java @@ -13,6 +13,8 @@ import android.view.ViewGroup; import android.webkit.CookieManager; import android.webkit.GeolocationPermissions; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; @@ -175,6 +177,96 @@ public void onScroll(int x, int y, int oldx, int oldy) { webView.setWebViewClient(webViewClient); webView.setWebChromeClient(new WebChromeClient() { + + @Override + public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { + Map args = new HashMap<>(); + args.put("url", url); + args.put("message", message); + FlutterWebviewPlugin.channel.invokeMethod("onJsAlert", args, new MethodChannel.Result() { + @Override + public void success(Object o) { + if (result != null) { + result.confirm(); + } + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + System.out.println("error"); + } + + @Override + public void notImplemented() { + System.out.println("notImplemented"); + } + }); + return true; + } + + @Override + public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) { + Map args = new HashMap<>(); + args.put("url", url); + args.put("message", message); + FlutterWebviewPlugin.channel.invokeMethod("onJsConfirm", args, new MethodChannel.Result() { + @Override + public void success(Object o) { + if (o instanceof Boolean) { + boolean boolResult = (Boolean) o; + if (result != null) { + if (boolResult) { + result.confirm(); + } else { + result.cancel(); + } + } + } + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + + } + + @Override + public void notImplemented() { + + } + }); + return true; + } + + @Override + public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) { + Map args = new HashMap<>(); + args.put("url", url); + args.put("message", message); + args.put("defaultText", defaultValue); + FlutterWebviewPlugin.channel.invokeMethod("onJsPrompt", args, new MethodChannel.Result() { + @Override + public void success(Object o) { + if (o instanceof String) { + String str = (String) o; + if (result != null) { + result.confirm(str); + } + } + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + + } + + @Override + public void notImplemented() { + + } + }); + return true; + } + //The undocumented magic method override //Eclipse will swear at you if you try to put @Override here // For Android 3.0+ diff --git a/example/lib/cupertino_dialogs.dart b/example/lib/cupertino_dialogs.dart new file mode 100644 index 00000000..4554e025 --- /dev/null +++ b/example/lib/cupertino_dialogs.dart @@ -0,0 +1,116 @@ +import 'package:flutter/cupertino.dart'; + +class MyCupertinoAlertDialog extends StatelessWidget { + final String message; + + MyCupertinoAlertDialog({this.message = ''}); + + @override + Widget build(BuildContext context) { + return CupertinoAlertDialog( + content: Text(message), + actions: [ + CupertinoDialogAction( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }), + ], + ); + } +} + +class CupertinoConfirmDialog extends StatelessWidget { + final String message; + + CupertinoConfirmDialog({this.message = ''}); + + @override + Widget build(BuildContext context) { + return CupertinoAlertDialog( + content: Text(message), + actions: [ + CupertinoDialogAction( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(false); + }), + CupertinoDialogAction( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(true); + }), + ], + ); + } +} + +class CupertinoPromptDialog extends StatefulWidget { + final String message; + final String defaultText; + + CupertinoPromptDialog({Key key, this.message, this.defaultText}) : super(key: key); + + @override + _PromptDialogState createState() => _PromptDialogState(); +} + +class _PromptDialogState extends State { + TextEditingController _controller; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(); + _controller.text = widget.defaultText; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return CupertinoAlertDialog( + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(widget.message), + Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), + child: CupertinoTextField( + controller: _controller, + cursorColor: CupertinoColors.inactiveGray, + style: const TextStyle( + fontSize: 16, + ), + maxLines: 1, + decoration: BoxDecoration( + color: CupertinoColors.white, + border: Border.all( + width: 1.2, + color: CupertinoColors.inactiveGray, + ), + borderRadius: BorderRadius.circular(8.0), + ), + ), + ) + ]), + actions: [ + CupertinoDialogAction( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(''); + }), + CupertinoDialogAction( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(_controller.text); + }), + ], + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index f28698dd..cef55066 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,9 +1,12 @@ import 'dart:async'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; +import 'material_dialogs.dart'; + const kAndroidUserAgent = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36'; @@ -124,6 +127,31 @@ class _MyHomePageState extends State { @override void initState() { super.initState(); + flutterWebViewPlugin.onJSAlert = (alert) async { + return await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: MyAlertDialog(message: alert.message))); + }; + flutterWebViewPlugin.onJSConfirm = (confirm) async { + return await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: ConfirmDialog(message: confirm.message)), + ); + }; + flutterWebViewPlugin.onJSPrompt = (prompt) async { + return await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: PromptDialog(message: prompt.message, defaultText: prompt.defaultText))); + }; flutterWebViewPlugin.close(); @@ -286,6 +314,41 @@ class _MyHomePageState extends State { child: const Text('Eval javascript alert()'), ), ElevatedButton( + onPressed: () { + final future = flutterWebViewPlugin.evalJavascript('alert("Hello World");'); + future.then((String result) { + setState(() { + _history.add('eval: $result'); + }); + }); + }, + child: const Text('Eval javascript alert()'), + ), + RaisedButton( + onPressed: () { + final future = flutterWebViewPlugin + .evalJavascript('confirm("Hello World");'); + future.then((String result) { + setState(() { + _history.add('eval: $result'); + }); + }); + }, + child: const Text('Eval javascript confirm()'), + ), + RaisedButton( + onPressed: () { + final future = flutterWebViewPlugin.evalJavascript( + 'prompt("Please enter your name", "Harry Potter");'); + future.then((String result) { + setState(() { + _history.add('eval: $result'); + }); + }); + }, + child: const Text('Eval javascript prompt()'), + ), + RaisedButton( onPressed: () { setState(() { _history.clear(); @@ -310,4 +373,4 @@ class _MyHomePageState extends State { ), ); } -} +} \ No newline at end of file diff --git a/example/lib/material_dialogs.dart b/example/lib/material_dialogs.dart new file mode 100644 index 00000000..f7450014 --- /dev/null +++ b/example/lib/material_dialogs.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; + +class MyAlertDialog extends StatelessWidget { + final String message; + + MyAlertDialog({this.message = ''}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + content: Text(message), + actions: [ + FlatButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }), + ], + ); + } +} + +class ConfirmDialog extends StatelessWidget { + final String message; + + ConfirmDialog({this.message = ''}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + content: Text(message), + actions: [ + FlatButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(false); + }), + FlatButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(true); + }), + ], + ); + } +} + +class PromptDialog extends StatefulWidget { + final String message; + final String defaultText; + + PromptDialog({Key key, this.message, this.defaultText = ''}) + : super(key: key); + + @override + _PromptDialogState createState() => _PromptDialogState(); +} + +class _PromptDialogState extends State { + TextEditingController _controller; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(); + _controller.text = widget.defaultText; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(widget.message), + TextField(controller: _controller), + ]), + actions: [ + FlatButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(''); + }), + FlatButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(_controller.text); + }), + ], + ); + } +} diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index fc266094..0e0bf719 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -480,55 +480,41 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { #pragma mark -- WKUIDelegate - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil - message:message - preferredStyle:UIAlertControllerStyleAlert]; - - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { - completionHandler(); - }]]; - - [self.viewController presentViewController:alert animated:YES completion:nil]; + [channel invokeMethod:@"onJsAlert" arguments:@{@"url": webView.URL.absoluteString, @"message": message} result:^(id _Nullable result) { + completionHandler(); + }]; } - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil - message:message - preferredStyle:UIAlertControllerStyleAlert]; - - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { - completionHandler(NO); - }]]; - - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - completionHandler(YES); - }]]; - - [self.viewController presentViewController:alert animated:YES completion:nil]; + [channel invokeMethod:@"onJsConfirm" arguments:@{@"url": webView.URL.absoluteString, @"message": message} result:^(id _Nullable result) { + if ([result isKindOfClass:[NSNumber class]]) { + NSNumber* b = result; + completionHandler([b boolValue]); + return; + } else if ([result isKindOfClass:[FlutterError class]]) { + FlutterError* flutterError = result; + NSLog(@"Error code: %@ message: %@ details: %@", + flutterError.code, flutterError.message, flutterError.details); + } + completionHandler(NO); + }]; } - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil - message:prompt - preferredStyle:UIAlertControllerStyleAlert]; - - [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = prompt; - textField.secureTextEntry = NO; - textField.text = defaultText; - }]; - - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { - completionHandler(nil); - }]]; - - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - completionHandler([alert.textFields.firstObject text]); - }]]; - - [self.viewController presentViewController:alert animated:YES completion:nil]; + [channel invokeMethod:@"onJsPrompt" arguments:@{@"url": webView.URL.absoluteString, @"message": prompt, @"defaultText": defaultText} result:^(id _Nullable result) { + if ([result isKindOfClass: [NSString class]]) { + NSString *str = result; + completionHandler(str); + return; + } else if ([result isKindOfClass:[FlutterError class]]) { + FlutterError* flutterError = result; + NSLog(@"Error code: %@ message: %@ details: %@", + flutterError.code, flutterError.message, flutterError.details); + } + completionHandler(@""); + }]; } @end diff --git a/lib/src/base.dart b/lib/src/base.dart index 2fdc0384..e0be8bf0 100644 --- a/lib/src/base.dart +++ b/lib/src/base.dart @@ -46,7 +46,7 @@ class FlutterWebviewPlugin { final Map _javascriptChannels = {}; - Future _handleMessages(MethodCall call) async { + Future _handleMessages(MethodCall call) async { switch (call.method) { case 'onBack': _onBack.add(null); @@ -63,6 +63,28 @@ class FlutterWebviewPlugin { case 'onScrollYChanged': _onScrollYChanged.add(call.arguments['yDirection']); break; + case 'onJsAlert': + if (onJSAlert != null) { + await onJSAlert(WebViewJSAlert( + url: call.arguments['url'], message: call.arguments['message'])); + } + return null; + case 'onJsConfirm': + if (onJSConfirm != null) { + final result = await onJSConfirm(WebViewJSConfirm( + url: call.arguments['url'], message: call.arguments['message'])); + return result; + } + return false; + case 'onJsPrompt': + if (onJSPrompt != null) { + final result = await onJSPrompt(WebViewJSPrompt( + url: call.arguments['url'], + message: call.arguments['message'], + defaultText: call.arguments['defaultText'])); + return result; + } + return ''; case 'onProgressChanged': _onProgressChanged.add(call.arguments['progress']); break; @@ -84,6 +106,12 @@ class FlutterWebviewPlugin { } } + Future Function(WebViewJSAlert alert) onJSAlert; + + Future Function(WebViewJSConfirm confirm) onJSConfirm; + + Future Function(WebViewJSPrompt prompt) onJSPrompt; + /// Listening the OnDestroy LifeCycle Event for Android Stream get onDestroy => _onDestroy.stream; @@ -376,3 +404,25 @@ class WebViewHttpError { final String url; final String code; } + +class WebViewJSAlert { + WebViewJSAlert({this.url, this.message}); + + final String url; + final String message; +} + +class WebViewJSConfirm { + WebViewJSConfirm({this.url, this.message}); + + final String url; + final String message; +} + +class WebViewJSPrompt { + WebViewJSPrompt({this.url, this.message, this.defaultText}); + + final String url; + final String message; + final String defaultText; +}