From 1e59c1830b506553dfcac8b2880cfadca85cde2a Mon Sep 17 00:00:00 2001 From: Ken Date: Sat, 9 Dec 2017 17:03:39 +0800 Subject: [PATCH 01/11] iOS: add delegate --- .../plugins/GeneratedPluginRegistrant.java | 13 -- example/ios/Runner.xcodeproj/project.pbxproj | 10 +- example/lib/main.dart | 34 ++++-- ios/Classes/FlutterWebviewPlugin.h | 2 +- ios/Classes/FlutterWebviewPlugin.m | 111 ++++++++++++++++-- lib/flutter_webview_plugin.dart | 64 ++++++---- 6 files changed, 175 insertions(+), 59 deletions(-) delete mode 100644 example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java diff --git a/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java deleted file mode 100644 index b8df97e3..00000000 --- a/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.flutter.plugins; - -import io.flutter.plugin.common.PluginRegistry; -import com.flutter_webview_plugin.FlutterWebviewPlugin; - -/** - * Generated file. Do not edit. - */ -public final class GeneratedPluginRegistrant { - public static void registerWith(PluginRegistry registry) { - FlutterWebviewPlugin.registerWith(registry.registrarFor("com.flutter_webview_plugin.FlutterWebviewPlugin")); - } -} diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 9151de46..6fd6ce7b 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -257,9 +257,14 @@ files = ( ); inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework", + "${BUILT_PRODUCTS_DIR}/flutter_webview_plugin/flutter_webview_plugin.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_webview_plugin.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -286,13 +291,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/example/lib/main.dart b/example/lib/main.dart index e8588f9b..580fffa4 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -40,8 +40,10 @@ class _MyHomePageState extends State { // On urlChanged stream StreamSubscription _onUrlChanged; + StreamSubscription _onStateChanged; + TextEditingController _ctrl = - new TextEditingController(text: "https://flutter.io"); + new TextEditingController(text: "https://flutter.io"); GlobalKey _scaffoldKey = new GlobalKey(); final _history = []; @@ -50,6 +52,14 @@ class _MyHomePageState extends State { initState() { super.initState(); + _onStateChanged = flutterWebviewPlugin.stateChanged.listen((String state) { + if (mounted) { + setState(() { + _history.add(state); + }); + } + }); + // Add a listener to on destroy WebView, so you can make came actions. _onDestroy = flutterWebviewPlugin.onDestroy.listen((_) { if (mounted) { @@ -93,21 +103,23 @@ class _MyHomePageState extends State { child: new TextField(controller: _ctrl), ), new RaisedButton( - onPressed: _onPressed, + onPressed: () { + flutterWebviewPlugin.launch(_ctrl.text, + fullScreen: false, + rect: new Rect.fromLTWH( + 0.0, 0.0, MediaQuery.of(context).size.width, 300.0)); + }, child: new Text("Open Webview"), ), + new RaisedButton( + onPressed: () { + flutterWebviewPlugin.launch(_ctrl.text, fullScreen: true); + }, + child: new Text("Open Fullscreen Webview"), + ), new Text(_history.join(", ")) ], ), ); } - - void _onPressed() { - try { - // This way you launch WebView with an url as a parameter. - flutterWebviewPlugin.launch(_ctrl.text); - } catch (e) { - print(e); - } - } } diff --git a/ios/Classes/FlutterWebviewPlugin.h b/ios/Classes/FlutterWebviewPlugin.h index cb36a043..04b460d1 100644 --- a/ios/Classes/FlutterWebviewPlugin.h +++ b/ios/Classes/FlutterWebviewPlugin.h @@ -5,5 +5,5 @@ static FlutterMethodChannel *channel; @interface FlutterWebviewPlugin : NSObject @property (nonatomic, retain) UIViewController *viewController; -@property (nonatomic, retain) WebviewController *webviewController; +@property (nonatomic, retain) UIWebView *webview; @end diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index 619d0bc1..5f5a571f 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -1,15 +1,29 @@ #import "FlutterWebviewPlugin.h" static NSString *const CHANNEL_NAME = @"flutter_webview_plugin"; +static NSString *const EVENT_CHANNEL_NAME = @"flutter_webview_plugin_event"; + +// UIWebViewDelegate +@interface FlutterWebviewPlugin() { + FlutterEventSink _eventSink; +} +@end @implementation FlutterWebviewPlugin + (void)registerWithRegistrar:(NSObject*)registrar { channel = [FlutterMethodChannel methodChannelWithName:CHANNEL_NAME binaryMessenger:[registrar messenger]]; + UIViewController *viewController = (UIViewController *)registrar.messenger; FlutterWebviewPlugin* instance = [[FlutterWebviewPlugin alloc] initWithViewController:viewController]; + [registrar addMethodCallDelegate:instance channel:channel]; + + FlutterEventChannel* event = + [FlutterEventChannel eventChannelWithName:EVENT_CHANNEL_NAME + binaryMessenger:[registrar messenger]]; + [event setStreamHandler:instance]; } - (instancetype)initWithViewController:(UIViewController *)viewController { @@ -22,7 +36,10 @@ - (instancetype)initWithViewController:(UIViewController *)viewController { - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([@"launch" isEqualToString:call.method]) { - [self showWebView:call]; + if (!self.webview) + [self initWebView:call]; + else + [self launch:call]; result(nil); } else if ([@"close" isEqualToString:call.method]) { [self closeWebView]; @@ -32,27 +49,97 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } -- (void)showWebView:(FlutterMethodCall*)call { +- (void)launch:(FlutterMethodCall*)call { NSString *url = call.arguments[@"url"]; - NSNumber *withJavascript = call.arguments[@"withJavascript"]; + + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; + [self.webview loadRequest:request]; +} + +- (void)initWebView:(FlutterMethodCall*)call { + // NSNumber *withJavascript = call.arguments[@"withJavascript"]; NSNumber *clearCache = call.arguments[@"clearCache"]; NSNumber *clearCookies = call.arguments[@"clearCookies"]; - NSNumber *fullScreen = call.arguments[@"fullScreen"]; + NSNumber *hidden = call.arguments[@"hidden"]; + NSDictionary *rect = call.arguments[@"rect"]; + + // + if ([clearCache boolValue]) { + [[NSURLCache sharedURLCache] removeAllCachedResponses]; + } - self.webviewController = [[WebviewController alloc] initWithUrl:url withJavascript:withJavascript clearCache:clearCache clearCookes:clearCookies]; + if ([clearCookies boolValue]) { + [[NSURLSession sharedSession] resetWithCompletionHandler:^{ + }]; + } - if ([fullScreen boolValue]) { - [self.viewController presentViewController:self.webviewController animated:YES completion:nil]; + CGRect rc; + if (rect) { + rc = CGRectMake([[rect valueForKey:@"left"] doubleValue], + [[rect valueForKey:@"top"] doubleValue], + [[rect valueForKey:@"width"] doubleValue], + [[rect valueForKey:@"height"] doubleValue]); } else { - UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:self.webviewController]; - [self.viewController presentModalViewController:navigation animated:YES]; + rc = self.viewController.view.bounds; } + + self.webview = [[UIWebView alloc] initWithFrame:rc]; + self.webview.delegate = self; + + if (!hidden || ![hidden boolValue]) + [self.viewController.view addSubview:self.webview]; + + [self launch:call]; } - (void)closeWebView { - [self.webviewController dismissViewControllerAnimated:YES completion:^{ - [channel invokeMethod:@"onDestroy" arguments:nil]; - }]; + [self.webview stopLoading]; + [self.webview removeFromSuperview]; + self.webview.delegate = nil; + self.webview = nil; } + +#pragma mark -- WebView Delegate +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { + [self sendStateEvent:[NSString stringWithFormat:@"shouldStart %@", request.URL]]; + return YES; +} +-(void)webViewDidStartLoad:(UIWebView *)webView { + [self sendStateEvent:@"startLoad"]; +} + +- (void)webViewDidFinishLoad:(UIWebView *)webView { + [self sendStateEvent:@"finishLoad"]; +} + +- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { + id data = [FlutterError errorWithCode:[NSString stringWithFormat:@"%ld", error.code] + message:error.localizedDescription + details:error.localizedFailureReason]; + [self sendStateEvent:data]; +} + +#pragma mark -- WkWebView Delegate + +#pragma mark -- FlutterStreamHandler impl + +- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { + _eventSink = eventSink; + return nil; +} + +- (FlutterError*)onCancelWithArguments:(id)arguments { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + _eventSink = nil; + return nil; +} + +- (void)sendStateEvent:(id)data { + // data should be @"" or [FlutterError] + if (!_eventSink) + return; + + _eventSink(data); +} @end diff --git a/lib/flutter_webview_plugin.dart b/lib/flutter_webview_plugin.dart index 44240717..535eff5a 100644 --- a/lib/flutter_webview_plugin.dart +++ b/lib/flutter_webview_plugin.dart @@ -1,33 +1,41 @@ import 'dart:async'; import 'package:flutter/services.dart'; +import 'package:flutter/material.dart'; const _kChannel = 'flutter_webview_plugin'; +const _kEvent = 'flutter_webview_plugin_event'; /// Singleton Class that communicate with a fullscreen Webview Instance /// Have to be instanciate after `runApp` called. class FlutterWebviewPlugin { - final MethodChannel _channel = const MethodChannel(_kChannel); + final MethodChannel _channel; + + final EventChannel _event; + Stream _stateChanged; + + Stream get stateChanged { + if (_stateChanged == null) { + _stateChanged = _event.receiveBroadcastStream(); + } + return _stateChanged; + } + final StreamController _onDestroy = new StreamController.broadcast(); final StreamController _onBackPressed = new StreamController.broadcast(); - /// TODO: iOS implementation final StreamController _onUrlChanged = new StreamController.broadcast(); - static FlutterWebviewPlugin _instance; - FlutterWebviewPlugin._() { - _init(); - } - - factory FlutterWebviewPlugin() => _instance ??= new FlutterWebviewPlugin._(); - - _init() { + FlutterWebviewPlugin() : + _channel = const MethodChannel(_kChannel), + _event = const EventChannel(_kEvent) { _channel.setMethodCallHandler(_handleMessages); } Future _handleMessages(MethodCall call) async { + print("_handleMessages $call"); switch (call.method) { case "onDestroy": _onDestroy.add(null); @@ -56,17 +64,31 @@ class FlutterWebviewPlugin { /// - [clearCache] clear the cache of the Webview /// - clearCookies] clear all cookies of the Webview Future launch(String url, - {bool withJavascript: true, - bool clearCache: false, - bool clearCookies: false, - bool fullScreen: true}) => - _channel.invokeMethod('launch', { - "url": url, - "withJavascript": withJavascript, - "clearCache": clearCache, - "clearCookies": clearCookies, - "fullScreen": fullScreen - }); + {bool withJavascript: true, + bool clearCache: false, + bool clearCookies: false, + bool hidden: false, + bool fullScreen: true, + Rect rect: null}) async { + Map args = { + "url": url, + "withJavascript": withJavascript, + "clearCache": clearCache, + "hidden": hidden, + "clearCookies": clearCookies, + "fullScreen": fullScreen + }; + if (!fullScreen) assert(rect != null); + if (rect != null) { + args["rect"] = { + "left": rect.left, + "right": rect.right, + "width": rect.width, + "height": rect.height + }; + } + await _channel.invokeMethod('launch', args); + } /// Close the Webview /// Will trigger the [onDestroy] event From cf43d748711fed9e9b27e6ee09a9486751983d92 Mon Sep 17 00:00:00 2001 From: Ken Date: Sat, 9 Dec 2017 17:09:04 +0800 Subject: [PATCH 02/11] add hidden in example --- example/lib/main.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index 580fffa4..708d2add 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -111,6 +111,12 @@ class _MyHomePageState extends State { }, child: new Text("Open Webview"), ), + new RaisedButton( + onPressed: () { + flutterWebviewPlugin.launch(_ctrl.text, hidden: true); + }, + child: new Text("Open 'hidden' Webview"), + ), new RaisedButton( onPressed: () { flutterWebviewPlugin.launch(_ctrl.text, fullScreen: true); From 72be4cbacca21eddb09a17752c84c05ff5d2faeb Mon Sep 17 00:00:00 2001 From: Ken Date: Sat, 9 Dec 2017 17:27:58 +0800 Subject: [PATCH 03/11] add eval Javascript --- example/lib/main.dart | 32 ++++++++++++++++++++++++------ ios/Classes/FlutterWebviewPlugin.m | 23 ++++++++++++++------- lib/flutter_webview_plugin.dart | 10 +++++++--- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 708d2add..41d52001 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -42,8 +42,12 @@ class _MyHomePageState extends State { StreamSubscription _onStateChanged; - TextEditingController _ctrl = + TextEditingController _urlCtrl = new TextEditingController(text: "https://flutter.io"); + + TextEditingController _codeCtrl = + new TextEditingController(text: "window.location.href"); + GlobalKey _scaffoldKey = new GlobalKey(); final _history = []; @@ -100,11 +104,11 @@ class _MyHomePageState extends State { children: [ new Container( padding: const EdgeInsets.all(24.0), - child: new TextField(controller: _ctrl), + child: new TextField(controller: _urlCtrl), ), new RaisedButton( onPressed: () { - flutterWebviewPlugin.launch(_ctrl.text, + flutterWebviewPlugin.launch(_urlCtrl.text, fullScreen: false, rect: new Rect.fromLTWH( 0.0, 0.0, MediaQuery.of(context).size.width, 300.0)); @@ -113,17 +117,33 @@ class _MyHomePageState extends State { ), new RaisedButton( onPressed: () { - flutterWebviewPlugin.launch(_ctrl.text, hidden: true); + flutterWebviewPlugin.launch(_urlCtrl.text, hidden: true); }, child: new Text("Open 'hidden' Webview"), ), new RaisedButton( onPressed: () { - flutterWebviewPlugin.launch(_ctrl.text, fullScreen: true); + flutterWebviewPlugin.launch(_urlCtrl.text, fullScreen: true); + }, + child: new Text("Open Fullscreen Webview"), + ), + new Container( + padding: const EdgeInsets.all(24.0), + child: new TextField(controller: _codeCtrl), + ), + new RaisedButton( + onPressed: () { + Future future = + flutterWebviewPlugin.evalJavascript(_codeCtrl.text); + future.then((String result) { + setState(() { + _history.add(result); + }); + }); }, child: new Text("Open Fullscreen Webview"), ), - new Text(_history.join(", ")) + new Text(_history.join("\n")) ], ), ); diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index 5f5a571f..8689d877 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -44,18 +44,13 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else if ([@"close" isEqualToString:call.method]) { [self closeWebView]; result(nil); + } else if ([@"eval" isEqualToString:call.method]) { + result([self evalJavascript:call]); } else { result(FlutterMethodNotImplemented); } } -- (void)launch:(FlutterMethodCall*)call { - NSString *url = call.arguments[@"url"]; - - NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; - [self.webview loadRequest:request]; -} - - (void)initWebView:(FlutterMethodCall*)call { // NSNumber *withJavascript = call.arguments[@"withJavascript"]; NSNumber *clearCache = call.arguments[@"clearCache"]; @@ -92,6 +87,20 @@ - (void)initWebView:(FlutterMethodCall*)call { [self launch:call]; } +- (void)launch:(FlutterMethodCall*)call { + NSString *url = call.arguments[@"url"]; + + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; + [self.webview loadRequest:request]; +} + +- (NSString *)evalJavascript:(FlutterMethodCall*)call { + NSString *code = call.arguments[@"code"]; + + NSString *result = [self.webview stringByEvaluatingJavaScriptFromString:code]; + return result; +} + - (void)closeWebView { [self.webview stopLoading]; [self.webview removeFromSuperview]; diff --git a/lib/flutter_webview_plugin.dart b/lib/flutter_webview_plugin.dart index 535eff5a..ed9cad39 100644 --- a/lib/flutter_webview_plugin.dart +++ b/lib/flutter_webview_plugin.dart @@ -28,9 +28,9 @@ class FlutterWebviewPlugin { final StreamController _onUrlChanged = new StreamController.broadcast(); - FlutterWebviewPlugin() : - _channel = const MethodChannel(_kChannel), - _event = const EventChannel(_kEvent) { + FlutterWebviewPlugin() + : _channel = const MethodChannel(_kChannel), + _event = const EventChannel(_kEvent) { _channel.setMethodCallHandler(_handleMessages); } @@ -90,6 +90,10 @@ class FlutterWebviewPlugin { await _channel.invokeMethod('launch', args); } + Future evalJavascript(String code) { + return _channel.invokeMethod('eval', {"code": code}); + } + /// Close the Webview /// Will trigger the [onDestroy] event Future close() => _channel.invokeMethod("close"); From 43a62d162c820239a15448a66779c4d141bd4bcf Mon Sep 17 00:00:00 2001 From: Ken Date: Sat, 9 Dec 2017 17:38:35 +0800 Subject: [PATCH 04/11] add flag for some scheme --- ios/Classes/FlutterWebviewPlugin.m | 12 +++++++++++- lib/flutter_webview_plugin.dart | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index 8689d877..2b83a973 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -6,6 +6,7 @@ // UIWebViewDelegate @interface FlutterWebviewPlugin() { FlutterEventSink _eventSink; + BOOL _enableAppScheme; } @end @@ -57,6 +58,7 @@ - (void)initWebView:(FlutterMethodCall*)call { NSNumber *clearCookies = call.arguments[@"clearCookies"]; NSNumber *hidden = call.arguments[@"hidden"]; NSDictionary *rect = call.arguments[@"rect"]; + _enableAppScheme = call.arguments[@"enableAppScheme"]; // if ([clearCache boolValue]) { @@ -112,8 +114,16 @@ - (void)closeWebView { #pragma mark -- WebView Delegate - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { [self sendStateEvent:[NSString stringWithFormat:@"shouldStart %@", request.URL]]; - return YES; + + if (_enableAppScheme) + return YES; + + // disable some scheme + return [request.URL.scheme isEqualToString:@"http"] || + [request.URL.scheme isEqualToString:@"https"] || + [request.URL.scheme isEqualToString:@"about"]; } + -(void)webViewDidStartLoad:(UIWebView *)webView { [self sendStateEvent:@"startLoad"]; } diff --git a/lib/flutter_webview_plugin.dart b/lib/flutter_webview_plugin.dart index ed9cad39..d0522b49 100644 --- a/lib/flutter_webview_plugin.dart +++ b/lib/flutter_webview_plugin.dart @@ -69,6 +69,7 @@ class FlutterWebviewPlugin { bool clearCookies: false, bool hidden: false, bool fullScreen: true, + bool enableAppScheme: true, Rect rect: null}) async { Map args = { "url": url, @@ -76,7 +77,8 @@ class FlutterWebviewPlugin { "clearCache": clearCache, "hidden": hidden, "clearCookies": clearCookies, - "fullScreen": fullScreen + "fullScreen": fullScreen, + "enableAppScheme": enableAppScheme }; if (!fullScreen) assert(rect != null); if (rect != null) { From 9679f13704b7f61511e69ecd4bef6ef85af149cb Mon Sep 17 00:00:00 2001 From: pedia Date: Tue, 12 Dec 2017 12:02:44 +0800 Subject: [PATCH 05/11] add more document add eval javascript sink onUrlChanged ... --- example/lib/main.dart | 23 ++++++++---- ios/Classes/FlutterWebviewPlugin.m | 14 ++++--- lib/flutter_webview_plugin.dart | 60 +++++++++++++++++++++++++++--- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 41d52001..140ebb37 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -32,7 +32,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { // Instance of WebView plugin - final FlutterWebviewPlugin flutterWebviewPlugin = new FlutterWebviewPlugin(); + final FlutterWebViewPlugin flutterWebviewPlugin = new FlutterWebViewPlugin(); // On destroy stream StreamSubscription _onDestroy; @@ -43,7 +43,7 @@ class _MyHomePageState extends State { StreamSubscription _onStateChanged; TextEditingController _urlCtrl = - new TextEditingController(text: "https://flutter.io"); + new TextEditingController(text: "http://github.com"); TextEditingController _codeCtrl = new TextEditingController(text: "window.location.href"); @@ -56,10 +56,10 @@ class _MyHomePageState extends State { initState() { super.initState(); - _onStateChanged = flutterWebviewPlugin.stateChanged.listen((String state) { + _onStateChanged = flutterWebviewPlugin.stateChanged.listen((dynamic state) { if (mounted) { setState(() { - _history.add(state); + _history.add("stateChanged: $state"); }); } }); @@ -77,7 +77,7 @@ class _MyHomePageState extends State { _onUrlChanged = flutterWebviewPlugin.onUrlChanged.listen((String url) { if (mounted) { setState(() { - _history.add(url); + _history.add("onUrlChanged: $url"); }); } }); @@ -113,7 +113,7 @@ class _MyHomePageState extends State { rect: new Rect.fromLTWH( 0.0, 0.0, MediaQuery.of(context).size.width, 300.0)); }, - child: new Text("Open Webview"), + child: new Text("Open Webview (rect)"), ), new RaisedButton( onPressed: () { @@ -137,11 +137,18 @@ class _MyHomePageState extends State { flutterWebviewPlugin.evalJavascript(_codeCtrl.text); future.then((String result) { setState(() { - _history.add(result); + _history.add("eval: $result"); }); }); }, - child: new Text("Open Fullscreen Webview"), + child: new Text("Eval some javascript"), + ), + new RaisedButton( + onPressed: () { + _history.clear(); + flutterWebviewPlugin.close(); + }, + child: new Text("Close"), ), new Text(_history.join("\n")) ], diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index 2b83a973..a5d5f6ab 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -108,12 +108,16 @@ - (void)closeWebView { [self.webview removeFromSuperview]; self.webview.delegate = nil; self.webview = nil; + [self sendEvent:@"destroy"]; } #pragma mark -- WebView Delegate - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { - [self sendStateEvent:[NSString stringWithFormat:@"shouldStart %@", request.URL]]; + NSArray *data = [NSArray arrayWithObjects:@"shouldStart", + request.URL.absoluteString, [NSNumber numberWithInt:navigationType], + nil]; + [self sendEvent:data]; if (_enableAppScheme) return YES; @@ -125,18 +129,18 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) } -(void)webViewDidStartLoad:(UIWebView *)webView { - [self sendStateEvent:@"startLoad"]; + [self sendEvent:@"startLoad"]; } - (void)webViewDidFinishLoad:(UIWebView *)webView { - [self sendStateEvent:@"finishLoad"]; + [self sendEvent:@"finishLoad"]; } - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { id data = [FlutterError errorWithCode:[NSString stringWithFormat:@"%ld", error.code] message:error.localizedDescription details:error.localizedFailureReason]; - [self sendStateEvent:data]; + [self sendEvent:data]; } #pragma mark -- WkWebView Delegate @@ -154,7 +158,7 @@ - (FlutterError*)onCancelWithArguments:(id)arguments { return nil; } -- (void)sendStateEvent:(id)data { +- (void)sendEvent:(id)data { // data should be @"" or [FlutterError] if (!_eventSink) return; diff --git a/lib/flutter_webview_plugin.dart b/lib/flutter_webview_plugin.dart index d0522b49..e5fd9695 100644 --- a/lib/flutter_webview_plugin.dart +++ b/lib/flutter_webview_plugin.dart @@ -6,17 +6,49 @@ import 'package:flutter/material.dart'; const _kChannel = 'flutter_webview_plugin'; const _kEvent = 'flutter_webview_plugin_event'; +// TODO: more genral state for iOS/android +enum WebViewState { startLoad, finishLoad } + +// copy from UIWebView.h +enum _WebViewNavigateType { + TypeLinkClicked, + TypeFormSubmitted, + TypeBackForward, + TypeReload, + TypeFormResubmitted, + TypeOther +} + /// Singleton Class that communicate with a fullscreen Webview Instance /// Have to be instanciate after `runApp` called. -class FlutterWebviewPlugin { +class FlutterWebViewPlugin { final MethodChannel _channel; final EventChannel _event; Stream _stateChanged; Stream get stateChanged { + assert(_WebViewNavigateType.TypeLinkClicked.index == 0); + assert(_WebViewNavigateType.TypeOther.index == 5); if (_stateChanged == null) { _stateChanged = _event.receiveBroadcastStream(); + _stateChanged.listen((var result) { + // the list like: [state, url, navtype] + if (result is List && result.length == 3) { + if (_WebViewNavigateType.TypeBackForward.index == result[2]) { + _onBackPressed.add(Null); + } else if (_WebViewNavigateType.TypeOther.index == result[2] || + _WebViewNavigateType.TypeLinkClicked.index == result[2] || + _WebViewNavigateType.TypeFormSubmitted.index == result[2]) { + // TODO: find out better way + _onUrlChanged.add(result[1]); + } + } else if (result is String) { + if (result == "destroy") { + _onDestroy.add(Null); + } + } + }); } return _stateChanged; } @@ -28,14 +60,13 @@ class FlutterWebviewPlugin { final StreamController _onUrlChanged = new StreamController.broadcast(); - FlutterWebviewPlugin() + FlutterWebViewPlugin() : _channel = const MethodChannel(_kChannel), _event = const EventChannel(_kEvent) { _channel.setMethodCallHandler(_handleMessages); } Future _handleMessages(MethodCall call) async { - print("_handleMessages $call"); switch (call.method) { case "onDestroy": _onDestroy.add(null); @@ -61,8 +92,27 @@ class FlutterWebviewPlugin { /// Start the Webview with [url] /// - [withJavascript] enable Javascript or not for the Webview + /// iOS WebView: Not implemented yet + /// android: Implemented. /// - [clearCache] clear the cache of the Webview - /// - clearCookies] clear all cookies of the Webview + /// iOS WebView: Not implemented yet + /// iOS WKWebView: will implement later + /// android: Implemented + /// - [clearCookies] clear all cookies of the Webview + /// iOS WebView: Not implemented yet + /// iOS WKWebView: will implement later + /// android: Implemented + /// - [hidden] not show + /// iOS WebView: not shown(addSubView) in ViewController + /// android: Implemented + /// [fullScreen]: show in full screen mode, default true + /// iOS WebView: without rect, show in full screen mode + /// android: Not implemented yet + /// [rect]: show in rect(not full screen) + /// iOS WebView: worked + /// android: Not implemented yet + /// [enableAppScheme]: false will enable all schemes, true only for + /// httt/https/about Future launch(String url, {bool withJavascript: true, bool clearCache: false, @@ -92,7 +142,7 @@ class FlutterWebviewPlugin { await _channel.invokeMethod('launch', args); } - Future evalJavascript(String code) { + Future evalJavascript(String code) { return _channel.invokeMethod('eval', {"code": code}); } From 09ae5ab2572812f7baa7cab3638bc8f8edc9940b Mon Sep 17 00:00:00 2001 From: pedia Date: Fri, 15 Dec 2017 17:45:56 +0800 Subject: [PATCH 06/11] fix document --- example/lib/main.dart | 8 ++++++-- ios/Classes/FlutterWebviewPlugin.m | 5 +++++ lib/flutter_webview_plugin.dart | 29 ++++++++++++++++++++--------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 140ebb37..926031b6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,6 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_webview_plugin/flutter_webview_plugin.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"; + void main() { runApp(new MyApp()); } @@ -46,7 +49,7 @@ class _MyHomePageState extends State { new TextEditingController(text: "http://github.com"); TextEditingController _codeCtrl = - new TextEditingController(text: "window.location.href"); + new TextEditingController(text: "window.navigator.userAgent"); GlobalKey _scaffoldKey = new GlobalKey(); @@ -111,7 +114,8 @@ class _MyHomePageState extends State { flutterWebviewPlugin.launch(_urlCtrl.text, fullScreen: false, rect: new Rect.fromLTWH( - 0.0, 0.0, MediaQuery.of(context).size.width, 300.0)); + 0.0, 0.0, MediaQuery.of(context).size.width, 300.0), + userAgent: kAndroidUserAgent); }, child: new Text("Open Webview (rect)"), ), diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index a5d5f6ab..d9989de2 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -59,6 +59,7 @@ - (void)initWebView:(FlutterMethodCall*)call { NSNumber *hidden = call.arguments[@"hidden"]; NSDictionary *rect = call.arguments[@"rect"]; _enableAppScheme = call.arguments[@"enableAppScheme"]; + NSString *userAgent = call.arguments[@"userAgent"]; // if ([clearCache boolValue]) { @@ -80,6 +81,10 @@ - (void)initWebView:(FlutterMethodCall*)call { rc = self.viewController.view.bounds; } + if (userAgent) { + [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}]; + } + self.webview = [[UIWebView alloc] initWithFrame:rc]; self.webview.delegate = self; diff --git a/lib/flutter_webview_plugin.dart b/lib/flutter_webview_plugin.dart index e5fd9695..890c9cac 100644 --- a/lib/flutter_webview_plugin.dart +++ b/lib/flutter_webview_plugin.dart @@ -24,6 +24,8 @@ enum _WebViewNavigateType { class FlutterWebViewPlugin { final MethodChannel _channel; + /// iOS WebView: Implemented + /// Android WebView: not implemented final EventChannel _event; Stream _stateChanged; @@ -96,23 +98,27 @@ class FlutterWebViewPlugin { /// android: Implemented. /// - [clearCache] clear the cache of the Webview /// iOS WebView: Not implemented yet - /// iOS WKWebView: will implement later + /// iOS WkWebView: TODO: later /// android: Implemented /// - [clearCookies] clear all cookies of the Webview /// iOS WebView: Not implemented yet - /// iOS WKWebView: will implement later + /// iOS WkWebView: will implement later /// android: Implemented /// - [hidden] not show /// iOS WebView: not shown(addSubView) in ViewController - /// android: Implemented + /// android: Not implemented yet. /// [fullScreen]: show in full screen mode, default true /// iOS WebView: without rect, show in full screen mode - /// android: Not implemented yet + /// android: Implemented /// [rect]: show in rect(not full screen) /// iOS WebView: worked /// android: Not implemented yet - /// [enableAppScheme]: false will enable all schemes, true only for - /// httt/https/about + /// [enableAppScheme]: false will enable all schemes, true only for httt/https/about + /// iOS WebView: worked + /// android: Not implemented yet + /// [userAgent]: set the User-Agent of WebView + /// iOS WebView: worked + /// android: Not implemented yet Future launch(String url, {bool withJavascript: true, bool clearCache: false, @@ -120,7 +126,8 @@ class FlutterWebViewPlugin { bool hidden: false, bool fullScreen: true, bool enableAppScheme: true, - Rect rect: null}) async { + Rect rect: null, + String userAgent: null}) async { Map args = { "url": url, "withJavascript": withJavascript, @@ -128,7 +135,8 @@ class FlutterWebViewPlugin { "hidden": hidden, "clearCookies": clearCookies, "fullScreen": fullScreen, - "enableAppScheme": enableAppScheme + "enableAppScheme": enableAppScheme, + "userAgent": userAgent }; if (!fullScreen) assert(rect != null); if (rect != null) { @@ -142,6 +150,8 @@ class FlutterWebViewPlugin { await _channel.invokeMethod('launch', args); } + /// iOS WebView: worked + /// android: Not implemented yet Future evalJavascript(String code) { return _channel.invokeMethod('eval', {"code": code}); } @@ -151,6 +161,7 @@ class FlutterWebViewPlugin { Future close() => _channel.invokeMethod("close"); /// Listening url changed - /// + /// iOS WebView: worked + /// android: worked Stream get onUrlChanged => _onUrlChanged.stream; } From a3ccf62e54545923276a3a43814a77fe7fc65a05 Mon Sep 17 00:00:00 2001 From: pedia Date: Sat, 16 Dec 2017 18:05:41 +0800 Subject: [PATCH 07/11] ios: remove event channel android: add rect, fullScreen, userAgent, eval --- .../FlutterWebviewPlugin.java | 169 ++++++++++++++++-- example/lib/main.dart | 9 +- ios/Classes/FlutterWebviewPlugin.m | 81 ++++----- lib/flutter_webview_plugin.dart | 94 ++++------ 4 files changed, 230 insertions(+), 123 deletions(-) diff --git a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java index 5a19504a..0fba5d43 100644 --- a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java +++ b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java @@ -1,10 +1,21 @@ package com.flutter_webview_plugin; -import android.content.Intent; + import android.app.Activity; import android.content.Context; +import android.graphics.Bitmap; +import android.os.Build; +import android.view.ViewGroup; +import android.view.View; +import android.webkit.CookieManager; +import android.webkit.ValueCallback; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; + +import java.util.HashMap; +import java.util.Map; -import io.flutter.app.FlutterActivity; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; @@ -15,13 +26,13 @@ */ public class FlutterWebviewPlugin implements MethodCallHandler { private Activity activity; + private WebView webView; public static MethodChannel channel; - private final int WEBVIEW_ACTIVITY_CODE = 1; private static final String CHANNEL_NAME = "flutter_webview_plugin"; public static void registerWith(PluginRegistry.Registrar registrar) { channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); - FlutterWebviewPlugin instance = new FlutterWebviewPlugin((Activity) registrar.activity()); + FlutterWebviewPlugin instance = new FlutterWebviewPlugin((Activity)registrar.activity()); channel.setMethodCallHandler(instance); } @@ -38,27 +49,163 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { case "close": close(call, result); break; + case "eval": + eval(call, result); + break; default: result.notImplemented(); break; } } + private void clearCookies() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().removeAllCookies(new ValueCallback() { + @Override + public void onReceiveValue(Boolean aBoolean) { + + } + }); + } else { + CookieManager.getInstance().removeAllCookie(); + } + } + + private void clearCache() { + webView.clearCache(true); + webView.clearFormData(); + } + + private WebViewClient setWebViewClient() { + WebViewClient webViewClient = new BrowserClient(); + webView.setWebViewClient(webViewClient); + return webViewClient; + } + + private void eval(String code, final MethodChannel.Result result) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript(code, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + result.success(value); + } + }); + } else { + webView.loadUrl(code); + } + } + + // @Override + protected void onDestroy() { + FlutterWebviewPlugin.channel.invokeMethod("onDestroy", null); + } + + // @Override + public void onBackPressed() { + if(webView.canGoBack()){ + webView.goBack(); + return; + } + FlutterWebviewPlugin.channel.invokeMethod("onBackPressed", null); + } + + private static int dp2px(Context context, float dp) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dp * scale +0.5f); + } + private void openUrl(MethodCall call, MethodChannel.Result result) { - Intent intent = new Intent(activity, WebviewActivity.class); + if (webView == null) { + webView = new WebView(activity); - intent.putExtra(WebviewActivity.URL_KEY, (String) call.argument("url")); - intent.putExtra(WebviewActivity.WITH_JAVASCRIPT_KEY, (boolean) call.argument("withJavascript")); - intent.putExtra(WebviewActivity.CLEAR_CACHE_KEY, (boolean) call.argument("clearCache")); - intent.putExtra(WebviewActivity.CLEAR_COOKIES_KEY, (boolean) call.argument("clearCookies")); + Map rc = call.argument("rect"); + if (rc != null) { + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + dp2px(activity, rc.get("width").intValue()), dp2px(activity, rc.get("height").intValue())); + params.setMargins(dp2px(activity, rc.get("left").intValue()), dp2px(activity, rc.get("top").intValue()), + 0, 0); + activity.addContentView(webView, params); + } + else if (!(boolean) call.argument("hidden")) { + activity.setContentView(webView); + } + + setWebViewClient(); + } - activity.startActivityForResult(intent, WEBVIEW_ACTIVITY_CODE); + webView.getSettings().setJavaScriptEnabled((boolean) call.argument("withJavascript")); + if ((boolean) call.argument("clearCache")) { + clearCache(); + } + + if ((boolean) call.argument("hidden")) { + webView.setVisibility(View.INVISIBLE); + } + + if ((boolean) call.argument("clearCookies")) { + clearCookies(); + } + + String userAgent = call.argument("userAgent"); + if (userAgent != null) { + webView.getSettings().setUserAgentString(userAgent); + } + + String url = (String) call.argument("url"); + webView.loadUrl(url); result.success(null); } private void close(MethodCall call, MethodChannel.Result result) { - activity.finishActivity(WEBVIEW_ACTIVITY_CODE); + ViewGroup vg = (ViewGroup)(webView.getParent()); + vg.removeView(webView); + webView = null; result.success(null); + + FlutterWebviewPlugin.channel.invokeMethod("onDestroy", null); + } + + private void eval(MethodCall call, final MethodChannel.Result result) { + String code = call.argument("code"); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript(code, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + result.success(value); + } + }); + } else { + // TODO: + webView.loadUrl(code); + } + } + + + private class BrowserClient extends WebViewClient { + private BrowserClient() { + super(); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + Map data = new HashMap<>(); + data.put("url", url); + FlutterWebviewPlugin.channel.invokeMethod("onUrlChanged", data); + + data.put("type", "startLoad"); + FlutterWebviewPlugin.channel.invokeMethod("onState", data); + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + Map data = new HashMap<>(); + data.put("url", url); + data.put("type", "finishLoad"); + FlutterWebviewPlugin.channel.invokeMethod("onState", data); + } } } diff --git a/example/lib/main.dart b/example/lib/main.dart index 926031b6..23c18ef9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -59,10 +59,11 @@ class _MyHomePageState extends State { initState() { super.initState(); - _onStateChanged = flutterWebviewPlugin.stateChanged.listen((dynamic state) { + _onStateChanged = + flutterWebviewPlugin.onStateChanged.listen((dynamic state) { if (mounted) { setState(() { - _history.add("stateChanged: $state"); + _history.add("state: $state"); }); } }); @@ -149,7 +150,9 @@ class _MyHomePageState extends State { ), new RaisedButton( onPressed: () { - _history.clear(); + setState(() { + _history.clear(); + }); flutterWebviewPlugin.close(); }, child: new Text("Close"), diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index d9989de2..51d2faf9 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -1,11 +1,9 @@ #import "FlutterWebviewPlugin.h" static NSString *const CHANNEL_NAME = @"flutter_webview_plugin"; -static NSString *const EVENT_CHANNEL_NAME = @"flutter_webview_plugin_event"; // UIWebViewDelegate -@interface FlutterWebviewPlugin() { - FlutterEventSink _eventSink; +@interface FlutterWebviewPlugin() { BOOL _enableAppScheme; } @end @@ -20,11 +18,6 @@ + (void)registerWithRegistrar:(NSObject*)registrar { FlutterWebviewPlugin* instance = [[FlutterWebviewPlugin alloc] initWithViewController:viewController]; [registrar addMethodCallDelegate:instance channel:channel]; - - FlutterEventChannel* event = - [FlutterEventChannel eventChannelWithName:EVENT_CHANNEL_NAME - binaryMessenger:[registrar messenger]]; - [event setStreamHandler:instance]; } - (instancetype)initWithViewController:(UIViewController *)viewController { @@ -62,34 +55,36 @@ - (void)initWebView:(FlutterMethodCall*)call { NSString *userAgent = call.arguments[@"userAgent"]; // - if ([clearCache boolValue]) { + if (clearCache != (id)[NSNull null] && [clearCache boolValue]) { [[NSURLCache sharedURLCache] removeAllCachedResponses]; } - if ([clearCookies boolValue]) { + if (clearCookies != (id)[NSNull null] && [clearCookies boolValue]) { [[NSURLSession sharedSession] resetWithCompletionHandler:^{ }]; } + if (userAgent != (id)[NSNull null]) { + [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}]; + } + CGRect rc; - if (rect) { + if (rect != nil) { rc = CGRectMake([[rect valueForKey:@"left"] doubleValue], - [[rect valueForKey:@"top"] doubleValue], - [[rect valueForKey:@"width"] doubleValue], - [[rect valueForKey:@"height"] doubleValue]); + [[rect valueForKey:@"top"] doubleValue], + [[rect valueForKey:@"width"] doubleValue], + [[rect valueForKey:@"height"] doubleValue]); } else { + // TODO: create top NavigatorController and push rc = self.viewController.view.bounds; } - if (userAgent) { - [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}]; - } - self.webview = [[UIWebView alloc] initWithFrame:rc]; self.webview.delegate = self; - if (!hidden || ![hidden boolValue]) - [self.viewController.view addSubview:self.webview]; + if (hidden != (id)[NSNull null] && [hidden boolValue]) + self.webview.hidden = YES; + [self.viewController.view addSubview:self.webview]; [self launch:call]; } @@ -113,16 +108,25 @@ - (void)closeWebView { [self.webview removeFromSuperview]; self.webview.delegate = nil; self.webview = nil; - [self sendEvent:@"destroy"]; + + // manually trigger onDestroy + [channel invokeMethod:@"onDestroy" arguments:nil]; } #pragma mark -- WebView Delegate - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { - NSArray *data = [NSArray arrayWithObjects:@"shouldStart", - request.URL.absoluteString, [NSNumber numberWithInt:navigationType], - nil]; - [self sendEvent:data]; + id data = @{@"url": request.URL.absoluteString, + @"type": @"shouldStart", + @"navigationType": [NSNumber numberWithInt:navigationType]}; + [channel invokeMethod:@"onState" arguments:data]; + + if (navigationType == UIWebViewNavigationTypeBackForward) + [channel invokeMethod:@"onBackPressed" arguments:nil]; + else { + id data = @{@"url": request.URL.absoluteString}; + [channel invokeMethod:@"onUrlChanged" arguments:data]; + } if (_enableAppScheme) return YES; @@ -134,40 +138,19 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *) } -(void)webViewDidStartLoad:(UIWebView *)webView { - [self sendEvent:@"startLoad"]; + [channel invokeMethod:@"onState" arguments:@{@"type": @"startLoad"}]; } - (void)webViewDidFinishLoad:(UIWebView *)webView { - [self sendEvent:@"finishLoad"]; + [channel invokeMethod:@"onState" arguments:@{@"type": @"finishLoad"}]; } - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { id data = [FlutterError errorWithCode:[NSString stringWithFormat:@"%ld", error.code] message:error.localizedDescription details:error.localizedFailureReason]; - [self sendEvent:data]; + [channel invokeMethod:@"onError" arguments:data]; } #pragma mark -- WkWebView Delegate - -#pragma mark -- FlutterStreamHandler impl - -- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { - _eventSink = eventSink; - return nil; -} - -- (FlutterError*)onCancelWithArguments:(id)arguments { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - _eventSink = nil; - return nil; -} - -- (void)sendEvent:(id)data { - // data should be @"" or [FlutterError] - if (!_eventSink) - return; - - _eventSink(data); -} @end diff --git a/lib/flutter_webview_plugin.dart b/lib/flutter_webview_plugin.dart index 890c9cac..be61dbea 100644 --- a/lib/flutter_webview_plugin.dart +++ b/lib/flutter_webview_plugin.dart @@ -4,57 +4,15 @@ import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; const _kChannel = 'flutter_webview_plugin'; -const _kEvent = 'flutter_webview_plugin_event'; // TODO: more genral state for iOS/android -enum WebViewState { startLoad, finishLoad } - -// copy from UIWebView.h -enum _WebViewNavigateType { - TypeLinkClicked, - TypeFormSubmitted, - TypeBackForward, - TypeReload, - TypeFormResubmitted, - TypeOther -} +enum WebViewState { shouldStart, startLoad, finishLoad } /// Singleton Class that communicate with a fullscreen Webview Instance /// Have to be instanciate after `runApp` called. class FlutterWebViewPlugin { final MethodChannel _channel; - /// iOS WebView: Implemented - /// Android WebView: not implemented - final EventChannel _event; - Stream _stateChanged; - - Stream get stateChanged { - assert(_WebViewNavigateType.TypeLinkClicked.index == 0); - assert(_WebViewNavigateType.TypeOther.index == 5); - if (_stateChanged == null) { - _stateChanged = _event.receiveBroadcastStream(); - _stateChanged.listen((var result) { - // the list like: [state, url, navtype] - if (result is List && result.length == 3) { - if (_WebViewNavigateType.TypeBackForward.index == result[2]) { - _onBackPressed.add(Null); - } else if (_WebViewNavigateType.TypeOther.index == result[2] || - _WebViewNavigateType.TypeLinkClicked.index == result[2] || - _WebViewNavigateType.TypeFormSubmitted.index == result[2]) { - // TODO: find out better way - _onUrlChanged.add(result[1]); - } - } else if (result is String) { - if (result == "destroy") { - _onDestroy.add(Null); - } - } - }); - } - return _stateChanged; - } - final StreamController _onDestroy = new StreamController.broadcast(); final StreamController _onBackPressed = new StreamController.broadcast(); @@ -62,9 +20,12 @@ class FlutterWebViewPlugin { final StreamController _onUrlChanged = new StreamController.broadcast(); - FlutterWebViewPlugin() - : _channel = const MethodChannel(_kChannel), - _event = const EventChannel(_kEvent) { + final StreamController _onStateChanged = + new StreamController.broadcast(); + + final StreamController _onError = new StreamController.broadcast(); + + FlutterWebViewPlugin() : _channel = const MethodChannel(_kChannel) { _channel.setMethodCallHandler(_handleMessages); } @@ -79,19 +40,37 @@ class FlutterWebViewPlugin { case "onUrlChanged": _onUrlChanged.add(call.arguments["url"]); break; + case "onState": + _onStateChanged.add(call.arguments); + break; + case "onError": + _onError.add(call.arguments); + break; } } - ////////////////////// - /// Listening the OnDestroy LifeCycle Event for Android - /// + /// content is Map for url Stream get onDestroy => _onDestroy.stream; + /// Listening url changed + /// iOS WebView: worked + /// android: worked + Stream get onUrlChanged => _onUrlChanged.stream; + /// Listening the onBackPressed Event for Android - /// + /// content null + /// iOS WebView: worked + /// android: worked Stream get onBackPressed => _onBackPressed.stream; + /// Listening the onState Event for iOS WebView and Android + /// content is Map for type: {shouldStart|startLoad|finishLoad} + /// more detail than other events + /// iOS WebView: worked + /// android: Not for now. + Stream get onStateChanged => _onStateChanged.stream; + /// Start the Webview with [url] /// - [withJavascript] enable Javascript or not for the Webview /// iOS WebView: Not implemented yet @@ -106,19 +85,19 @@ class FlutterWebViewPlugin { /// android: Implemented /// - [hidden] not show /// iOS WebView: not shown(addSubView) in ViewController - /// android: Not implemented yet. + /// android: Implemented /// [fullScreen]: show in full screen mode, default true /// iOS WebView: without rect, show in full screen mode /// android: Implemented /// [rect]: show in rect(not full screen) /// iOS WebView: worked - /// android: Not implemented yet + /// android: Implemented /// [enableAppScheme]: false will enable all schemes, true only for httt/https/about /// iOS WebView: worked /// android: Not implemented yet /// [userAgent]: set the User-Agent of WebView /// iOS WebView: worked - /// android: Not implemented yet + /// android: Implemented Future launch(String url, {bool withJavascript: true, bool clearCache: false, @@ -142,7 +121,7 @@ class FlutterWebViewPlugin { if (rect != null) { args["rect"] = { "left": rect.left, - "right": rect.right, + "top": rect.top, "width": rect.width, "height": rect.height }; @@ -151,7 +130,7 @@ class FlutterWebViewPlugin { } /// iOS WebView: worked - /// android: Not implemented yet + /// android: implemented Future evalJavascript(String code) { return _channel.invokeMethod('eval', {"code": code}); } @@ -159,9 +138,4 @@ class FlutterWebViewPlugin { /// Close the Webview /// Will trigger the [onDestroy] event Future close() => _channel.invokeMethod("close"); - - /// Listening url changed - /// iOS WebView: worked - /// android: worked - Stream get onUrlChanged => _onUrlChanged.stream; } From 04a5cd1f60dfbdffcf9f36724dc80412ebe4cbd0 Mon Sep 17 00:00:00 2001 From: pedia Date: Tue, 19 Dec 2017 09:09:38 +0800 Subject: [PATCH 08/11] fix Android: crash in close a hidden webview --- .../com/flutter_webview_plugin/FlutterWebviewPlugin.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java index 0fba5d43..7ec8bcc6 100644 --- a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java +++ b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java @@ -158,8 +158,10 @@ else if (!(boolean) call.argument("hidden")) { } private void close(MethodCall call, MethodChannel.Result result) { - ViewGroup vg = (ViewGroup)(webView.getParent()); - vg.removeView(webView); + if (View.VISIBLE == webView.getVisibility()) { + ViewGroup vg = (ViewGroup) (webView.getParent()); + vg.removeView(webView); + } webView = null; result.success(null); From 0ac9e31467ce591f504e159dd52143ece7c99209 Mon Sep 17 00:00:00 2001 From: pedia Date: Tue, 19 Dec 2017 09:16:22 +0800 Subject: [PATCH 09/11] remove event of stateChanged subscribe in demo --- example/lib/main.dart | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 23c18ef9..37146b0c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -43,8 +43,6 @@ class _MyHomePageState extends State { // On urlChanged stream StreamSubscription _onUrlChanged; - StreamSubscription _onStateChanged; - TextEditingController _urlCtrl = new TextEditingController(text: "http://github.com"); @@ -59,15 +57,6 @@ class _MyHomePageState extends State { initState() { super.initState(); - _onStateChanged = - flutterWebviewPlugin.onStateChanged.listen((dynamic state) { - if (mounted) { - setState(() { - _history.add("state: $state"); - }); - } - }); - // Add a listener to on destroy WebView, so you can make came actions. _onDestroy = flutterWebviewPlugin.onDestroy.listen((_) { if (mounted) { From 77bf752297dc7795382594d74554e480488e9796 Mon Sep 17 00:00:00 2001 From: pedia Date: Tue, 19 Dec 2017 09:16:59 +0800 Subject: [PATCH 10/11] ready to release --- pubspec.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b713c17e..3a4af9f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,8 +3,9 @@ description: Plugin that allow Flutter to communicate with a native Webview. authors: - Hadrien Lejard - Toufik Zitouni +- Pedia homepage: https://github.com/dart-flitter/flutter_webview_plugin -version: 0.0.9+1 +version: 0.0.10 flutter: plugin: From 0bdb71ac11e5af10b2817987f9f06caa6490f363 Mon Sep 17 00:00:00 2001 From: pedia Date: Tue, 19 Dec 2017 09:28:40 +0800 Subject: [PATCH 11/11] add changelog for new release --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f348c4..5f8cb262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 0.0.10 + +- iOS: add Delegate, same as the event of android. + +- iOS && Android: + + - eval javascript + - user agent setting + - state change event + - embed in rectangle(not fullscreen) + - hidden webview + # 0.0.9 - Android: remove the need to use FlutterActivity as base activity