Browse Source

Merge pull request #20 from pedia/master

iOS delegate and some features
Hadrien Lejard 8 years ago
parent
commit
0e62672864

+ 12 - 0
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

+ 160 - 11
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 @@ import io.flutter.plugin.common.PluginRegistry;
  */
 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,165 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
       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<Boolean>() {
+        @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<String>() {
+        @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);
+
+      Map<String, Number> 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();
+    }
+
+    webView.getSettings().setJavaScriptEnabled((boolean) call.argument("withJavascript"));
+
+    if ((boolean) call.argument("clearCache")) {
+      clearCache();
+    }
+
+    if ((boolean) call.argument("hidden")) {
+      webView.setVisibility(View.INVISIBLE);
+    }
 
-    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"));
+    if ((boolean) call.argument("clearCookies")) {
+      clearCookies();
+    }
 
-    activity.startActivityForResult(intent, WEBVIEW_ACTIVITY_CODE);
+    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);
+    if (View.VISIBLE == webView.getVisibility()) {
+      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<String>() {
+        @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<String, Object> 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<String, Object> data = new HashMap<>();
+      data.put("url", url);
+      data.put("type", "finishLoad");
+      FlutterWebviewPlugin.channel.invokeMethod("onState", data);
+    }
   }
 }

+ 0 - 13
example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java

@@ -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"));
-  }
-}

+ 9 - 1
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 */

+ 58 - 17
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());
 }
@@ -32,7 +35,7 @@ class MyHomePage extends StatefulWidget {
 
 class _MyHomePageState extends State<MyHomePage> {
   // Instance of WebView plugin
-  final FlutterWebviewPlugin flutterWebviewPlugin = new FlutterWebviewPlugin();
+  final FlutterWebViewPlugin flutterWebviewPlugin = new FlutterWebViewPlugin();
 
   // On destroy stream
   StreamSubscription _onDestroy;
@@ -40,8 +43,12 @@ class _MyHomePageState extends State<MyHomePage> {
   // On urlChanged stream
   StreamSubscription<String> _onUrlChanged;
 
-  TextEditingController _ctrl =
-  new TextEditingController(text: "https://flutter.io");
+  TextEditingController _urlCtrl =
+      new TextEditingController(text: "http://github.com");
+
+  TextEditingController _codeCtrl =
+      new TextEditingController(text: "window.navigator.userAgent");
+
   GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
 
   final _history = [];
@@ -63,7 +70,7 @@ class _MyHomePageState extends State<MyHomePage> {
     _onUrlChanged = flutterWebviewPlugin.onUrlChanged.listen((String url) {
       if (mounted) {
         setState(() {
-          _history.add(url);
+          _history.add("onUrlChanged: $url");
         });
       }
     });
@@ -90,24 +97,58 @@ class _MyHomePageState extends State<MyHomePage> {
         children: [
           new Container(
             padding: const EdgeInsets.all(24.0),
-            child: new TextField(controller: _ctrl),
+            child: new TextField(controller: _urlCtrl),
+          ),
+          new RaisedButton(
+            onPressed: () {
+              flutterWebviewPlugin.launch(_urlCtrl.text,
+                  fullScreen: false,
+                  rect: new Rect.fromLTWH(
+                      0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
+                  userAgent: kAndroidUserAgent);
+            },
+            child: new Text("Open Webview (rect)"),
+          ),
+          new RaisedButton(
+            onPressed: () {
+              flutterWebviewPlugin.launch(_urlCtrl.text, hidden: true);
+            },
+            child: new Text("Open 'hidden' Webview"),
+          ),
+          new RaisedButton(
+            onPressed: () {
+              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: _onPressed,
-            child: new Text("Open Webview"),
+            onPressed: () {
+              Future<String> future =
+                  flutterWebviewPlugin.evalJavascript(_codeCtrl.text);
+              future.then((String result) {
+                setState(() {
+                  _history.add("eval: $result");
+                });
+              });
+            },
+            child: new Text("Eval some javascript"),
           ),
-          new Text(_history.join(", "))
+          new RaisedButton(
+            onPressed: () {
+              setState(() {
+                _history.clear();
+              });
+              flutterWebviewPlugin.close();
+            },
+            child: new Text("Close"),
+          ),
+          new Text(_history.join("\n"))
         ],
       ),
     );
   }
-
-  void _onPressed() {
-    try {
-      // This way you launch WebView with an url as a parameter.
-      flutterWebviewPlugin.launch(_ctrl.text);
-    } catch (e) {
-      print(e);
-    }
-  }
 }

+ 1 - 1
ios/Classes/FlutterWebviewPlugin.h

@@ -5,5 +5,5 @@ static FlutterMethodChannel *channel;
 
 @interface FlutterWebviewPlugin : NSObject<FlutterPlugin>
 @property (nonatomic, retain) UIViewController *viewController;
-@property (nonatomic, retain) WebviewController *webviewController;
+@property (nonatomic, retain) UIWebView *webview;
 @end

+ 111 - 13
ios/Classes/FlutterWebviewPlugin.m

@@ -2,13 +2,21 @@
 
 static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
 
+// UIWebViewDelegate
+@interface FlutterWebviewPlugin() <UIWebViewDelegate> {
+    BOOL _enableAppScheme;
+}
+@end
+
 @implementation FlutterWebviewPlugin
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)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];
 }
 
@@ -22,37 +30,127 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
 
 - (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];
         result(nil);
+    } else if ([@"eval" isEqualToString:call.method]) {
+        result([self evalJavascript:call]);
     } else {
         result(FlutterMethodNotImplemented);
     }
 }
 
-- (void)showWebView:(FlutterMethodCall*)call {
-    NSString *url = call.arguments[@"url"];
-    NSNumber *withJavascript = call.arguments[@"withJavascript"];
+- (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"];
+    _enableAppScheme = call.arguments[@"enableAppScheme"];
+    NSString *userAgent = call.arguments[@"userAgent"];
+    
+    //
+    if (clearCache != (id)[NSNull null] && [clearCache boolValue]) {
+        [[NSURLCache sharedURLCache] removeAllCachedResponses];
+    }
+    
+    if (clearCookies != (id)[NSNull null] && [clearCookies boolValue]) {
+        [[NSURLSession sharedSession] resetWithCompletionHandler:^{
+        }];
+    }
     
-    self.webviewController = [[WebviewController alloc] initWithUrl:url withJavascript:withJavascript clearCache:clearCache clearCookes:clearCookies];
+    if (userAgent != (id)[NSNull null]) {
+        [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}];
+    }
     
-    if ([fullScreen boolValue]) {
-        [self.viewController presentViewController:self.webviewController animated:YES completion:nil];
+    CGRect rc;
+    if (rect != nil) {
+        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];
+        // TODO: create top NavigatorController and push
+        rc = self.viewController.view.bounds;
     }
+    
+    self.webview = [[UIWebView alloc] initWithFrame:rc];
+    self.webview.delegate = self;
+    
+    if (hidden != (id)[NSNull null] && [hidden boolValue])
+        self.webview.hidden = YES;
+    [self.viewController.view addSubview:self.webview];
+    
+    [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.webviewController dismissViewControllerAnimated:YES completion:^{
-        [channel invokeMethod:@"onDestroy" arguments:nil];
-    }];
+    [self.webview stopLoading];
+    [self.webview removeFromSuperview];
+    self.webview.delegate = nil;
+    self.webview = nil;
+    
+    // manually trigger onDestroy
+    [channel invokeMethod:@"onDestroy" arguments:nil];
+}
+
+
+#pragma mark -- WebView Delegate
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
+    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;
+
+    // disable some scheme
+    return [request.URL.scheme isEqualToString:@"http"] ||
+            [request.URL.scheme isEqualToString:@"https"] ||
+            [request.URL.scheme isEqualToString:@"about"];
+}
+
+-(void)webViewDidStartLoad:(UIWebView *)webView {
+    [channel invokeMethod:@"onState" arguments:@{@"type": @"startLoad"}];
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+    [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];
+    [channel invokeMethod:@"onError" arguments:data];
 }
 
+#pragma mark -- WkWebView Delegate
 @end

+ 92 - 29
lib/flutter_webview_plugin.dart

@@ -1,29 +1,31 @@
 import 'dart:async';
 
 import 'package:flutter/services.dart';
+import 'package:flutter/material.dart';
 
 const _kChannel = 'flutter_webview_plugin';
 
+// TODO: more genral state for iOS/android
+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 = const MethodChannel(_kChannel);
+class FlutterWebViewPlugin {
+  final MethodChannel _channel;
+
   final StreamController<Null> _onDestroy = new StreamController.broadcast();
   final StreamController<Null> _onBackPressed =
       new StreamController.broadcast();
 
-  /// TODO: iOS implementation
   final StreamController<String> _onUrlChanged =
       new StreamController.broadcast();
 
-  static FlutterWebviewPlugin _instance;
-  FlutterWebviewPlugin._() {
-    _init();
-  }
+  final StreamController<Null> _onStateChanged =
+      new StreamController.broadcast();
 
-  factory FlutterWebviewPlugin() => _instance ??= new FlutterWebviewPlugin._();
+  final StreamController<Null> _onError = new StreamController.broadcast();
 
-  _init() {
+  FlutterWebViewPlugin() : _channel = const MethodChannel(_kChannel) {
     _channel.setMethodCallHandler(_handleMessages);
   }
 
@@ -38,41 +40,102 @@ 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<Null> get onDestroy => _onDestroy.stream;
 
+  /// Listening url changed
+  /// iOS WebView: worked
+  /// android: worked
+  Stream<Null> get onUrlChanged => _onUrlChanged.stream;
+
   /// Listening the onBackPressed Event for Android
-  ///
+  /// content null
+  /// iOS WebView: worked
+  /// android: worked
   Stream<Null> 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<Null> get onStateChanged => _onStateChanged.stream;
+
   /// 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: TODO: 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: Implemented
+  ///   [rect]: show in rect(not full screen)
+  ///     iOS WebView: worked
+  ///     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: Implemented
   Future<Null> 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,
+      bool enableAppScheme: true,
+      Rect rect: null,
+      String userAgent: null}) async {
+    Map<String, dynamic> args = {
+      "url": url,
+      "withJavascript": withJavascript,
+      "clearCache": clearCache,
+      "hidden": hidden,
+      "clearCookies": clearCookies,
+      "fullScreen": fullScreen,
+      "enableAppScheme": enableAppScheme,
+      "userAgent": userAgent
+    };
+    if (!fullScreen) assert(rect != null);
+    if (rect != null) {
+      args["rect"] = {
+        "left": rect.left,
+        "top": rect.top,
+        "width": rect.width,
+        "height": rect.height
+      };
+    }
+    await _channel.invokeMethod('launch', args);
+  }
+
+  /// iOS WebView: worked
+  /// android: implemented
+  Future<String> evalJavascript(String code) {
+    return _channel.invokeMethod('eval', {"code": code});
+  }
 
   /// Close the Webview
   /// Will trigger the [onDestroy] event
   Future<Null> close() => _channel.invokeMethod("close");
-
-  /// Listening url changed
-  ///
-  Stream<String> get onUrlChanged => _onUrlChanged.stream;
 }

+ 2 - 1
pubspec.yaml

@@ -3,8 +3,9 @@ description: Plugin that allow Flutter to communicate with a native Webview.
 authors:
 - Hadrien Lejard <hadrien.lejard@gmail.com>
 - Toufik Zitouni <toufiksapps@gmail.com>
+- Pedia <kpedia@163.com>
 homepage: https://github.com/dart-flitter/flutter_webview_plugin
-version: 0.0.9+1
+version: 0.0.10
 
 flutter:
   plugin: