Browse Source

add WebviewScaffold

Lejard Hadrien 7 years ago
parent
commit
dba0ad0926

+ 5 - 3
CHANGELOG.md

@@ -1,15 +1,17 @@
-# 0.0.10
+# 0.1.0
 
 - iOS && Android:
     - get cookies
     - eval javascript
     - user agent setting
     - state change event
-    - embed in rectangle(not fullscreen)
+    - embed in rectangle or fullscreen if null
     - hidden webview
     
 - Android
-    - adding Activity in manifest is no mandatory when not using fullScreen
+    - adding Activity in manifest is not needed anymore
+    
+- Add `WebviewScaffold`
 
 # 0.0.9
 

+ 2 - 2
LICENSE

@@ -1,4 +1,4 @@
-// Copyright 2017 Your Company. All rights reserved.
+// Copyright 2017 Hadrien Lejard. All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
 // modification, are permitted provided that the following conditions are
@@ -10,7 +10,7 @@
 // copyright notice, this list of conditions and the following disclaimer
 // in the documentation and/or other materials provided with the
 // distribution.
-//    * Neither the name of Your Company nor the names of its
+//    * Neither the name of Hadrien Lejard nor the names of its
 // contributors may be used to endorse or promote products derived from
 // this software without specific prior written permission.
 //

+ 47 - 25
README.md

@@ -8,45 +8,45 @@ For help getting started with Flutter, view our online [documentation](http://fl
 
 ### How it works
 
-#### Launch WebView Fullscreen (default)
+#### Launch WebView Fullscreen with Flutter navigation
 
-On Android, add the Activity to you `AndroidManifest.xml`:
-
-```xml
-<activity android:name="com.flutter_webview_plugin.WebviewActivity" android:parentActivityName=".MainActivity"/>
+```dart
+new MaterialApp(
+      routes: {
+        "/": (_) => new WebviewScaffold(
+              url: "https://www.google.com",
+              appBar: new AppBar(
+                title: new Text("Widget webview"),
+              ),
+            )
+      },
+    );
 ```
 
-***For Android, it will launch a new Activity inside the App with the Webview inside. Does not allow to integrate a Webview inside a Flutter Widget***
-
-***For IOS, it will launch a new UIViewController inside the App with the UIWebView inside. Does not allow to integrate a Webview inside a Flutter Widget***
-
+`FlutterWebviewPlugin` provide a singleton instance linked to one unique webview,
+so you can take control of the webview from anywhere in the app
 
+listen for events
 ```dart
-final flutterWebviewPlugin = new FlutterWebviewPlugin();  
+final flutterWebviewPlugin = new FlutterWebviewPlugin();
 
-flutterWebviewPlugin.launch(url);  
+flutterWebviewPlugin.onUrlChanged.listen((String url) {
+  
+});
 ```
 
-#### Close launched WebView
+#### Hidden WebView
 
 ```dart
 final flutterWebviewPlugin = new FlutterWebviewPlugin();  
 
-flutterWebviewPlugin.launch(url);  
-
-....
-
-// Close WebView.
-// This will also emit the onDestroy event.
-flutterWebviewPlugin.close();
+flutterWebviewPlugin.launch(url, hidden: true);
 ```
 
-#### Hidden webView
+#### Close launched WebView
 
 ```dart
-final flutterWebviewPlugin = new FlutterWebviewPlugin();  
-
-flutterWebviewPlugin.launch(url, hidden: true);
+flutterWebviewPlugin.close();
 ```
 
 #### Webview inside custom Rectangle
@@ -67,8 +67,30 @@ flutterWebviewPlugin.launch(url,
 
 - `Stream<Null>` onDestroy
 - `Stream<String>` onUrlChanged
-- `Stream<Null>` onBackPressed
 - `Stream<WebViewStateChanged>` onStateChanged
+- `Stream<String>` onError
 
 ***Don't forget to dispose webview***
-`flutterWebviewPlugin.dispose()`
+`flutterWebviewPlugin.dispose()`
+
+### Webview Functions
+
+```dart
+Future<Null> launch(String url,
+         {bool withJavascript: true,
+         bool clearCache: false,
+         bool clearCookies: false,
+         bool hidden: false,
+         bool enableAppScheme: true,
+         Rect rect: null,
+         String userAgent: null});
+```
+```dart
+Future<String> evalJavascript(String code);
+```
+```dart
+Future<Map<String, dynamic>> getCookies();
+```
+```dart
+Future<Null> resize(Rect rect);
+```

+ 57 - 36
android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java

@@ -3,7 +3,8 @@ package com.flutter_webview_plugin;
 
 import android.app.Activity;
 import android.content.Context;
-import android.content.Intent;
+import android.graphics.Point;
+import android.view.Display;
 import android.widget.FrameLayout;
 
 import java.util.Map;
@@ -17,16 +18,14 @@ import io.flutter.plugin.common.PluginRegistry;
  * FlutterWebviewPlugin
  */
 public class FlutterWebviewPlugin implements MethodCallHandler {
-    private final int WEBVIEW_ACTIVITY_CODE = 1;
-
     private Activity activity;
     private WebviewManager webViewManager;
-    public static MethodChannel channel;
+    static MethodChannel channel;
     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(registrar.activity());
         channel.setMethodCallHandler(instance);
     }
 
@@ -46,6 +45,9 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
             case "eval":
                 eval(call, result);
                 break;
+            case "resize":
+                resize(call, result);
+                break;
             default:
                 result.notImplemented();
                 break;
@@ -53,40 +55,51 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
     }
 
     private void openUrl(MethodCall call, MethodChannel.Result result) {
-        if ((boolean) call.argument("fullScreen") && !(boolean) call.argument("hidden")) {
-            Intent intent = new Intent(activity, WebviewActivity.class);
-            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"));
-            activity.startActivityForResult(intent, WEBVIEW_ACTIVITY_CODE);
-        } else {
-            if (webViewManager == null) {
-                webViewManager = new WebviewManager(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(webViewManager.webView, params);
-            } else if (!(boolean) call.argument("hidden")) {
-                activity.setContentView(webViewManager.webView);
-            }
-
-            webViewManager.openUrl((boolean) call.argument("withJavascript"),
-                    (boolean) call.argument("clearCache"),
-                    (boolean) call.argument("hidden"),
-                    (boolean) call.argument("clearCookies"),
-                    (String) call.argument("userAgent"),
-                    (String) call.argument("url")
-            );
+        boolean hidden = call.argument("hidden");
+        String url = call.argument("url");
+        String userAgent = call.argument("userAgent");
+        boolean withJavascript = call.argument("withJavascript");
+        boolean clearCache = call.argument("clearCache");
+        boolean clearCookies = call.argument("clearCookies");
+
+        if (webViewManager == null) {
+            webViewManager = new WebviewManager(activity);
         }
+
+        FrameLayout.LayoutParams params = buildLayoutParams(call);
+
+        activity.addContentView(webViewManager.webView, params);
+
+        webViewManager.openUrl(withJavascript,
+                clearCache,
+                hidden,
+                clearCookies,
+                userAgent,
+                url
+        );
         result.success(null);
     }
 
+    private FrameLayout.LayoutParams buildLayoutParams(MethodCall call) {
+        Map<String, Number> rc = call.argument("rect");
+        FrameLayout.LayoutParams params;
+        if (rc != null) {
+            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);
+        } else {
+            Display display = activity.getWindowManager().getDefaultDisplay();
+            Point size = new Point();
+            display.getSize(size);
+            int width = size.x;
+            int height = size.y;
+            params = new FrameLayout.LayoutParams(width, height);
+        }
+
+        return params;
+    }
+
     private void close(MethodCall call, MethodChannel.Result result) {
         if (webViewManager != null) {
             webViewManager.close(call, result);
@@ -100,7 +113,15 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
         }
     }
 
-    private static int dp2px(Context context, float dp) {
+    private void resize(MethodCall call, final MethodChannel.Result result) {
+        if (webViewManager != null) {
+            FrameLayout.LayoutParams params = buildLayoutParams(call);
+            webViewManager.resize(params);
+        }
+        result.success(null);
+    }
+
+    private int dp2px(Context context, float dp) {
         final float scale = context.getResources().getDisplayMetrics().density;
         return (int) (dp * scale + 0.5f);
     }

+ 0 - 68
android/src/main/java/com/flutter_webview_plugin/WebviewActivity.java

@@ -1,68 +0,0 @@
-package com.flutter_webview_plugin;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import io.flutter.plugin.common.MethodCall;
-import io.flutter.plugin.common.MethodChannel;
-
-/**
- * Created by lejard_h on 23/04/2017.
- */
-
-public class WebviewActivity extends Activity implements MethodChannel.MethodCallHandler {
-
-    static public final String URL_KEY = "URL";
-    static public final String CLEAR_CACHE_KEY = "CLEAR_CACHE";
-    static public final String CLEAR_COOKIES_KEY = "CLEAR_COOKIES";
-    static public final String WITH_JAVASCRIPT_KEY = "WITH_JAVASCRIPT";
-    static public final String USER_AGENT_KEY = "USER_AGENT";
-
-    static WebviewManager webViewManager;
-
-    public WebviewActivity() {
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        webViewManager = new WebviewManager(this);
-        setContentView(webViewManager.webView);
-        webViewManager.openUrl(getIntent().getBooleanExtra(WITH_JAVASCRIPT_KEY, true),
-                getIntent().getBooleanExtra(CLEAR_CACHE_KEY, false),
-                false,
-                getIntent().getBooleanExtra(CLEAR_COOKIES_KEY, false),
-                getIntent().getStringExtra(USER_AGENT_KEY),
-                getIntent().getStringExtra(URL_KEY)
-                );
-    }
-
-    @Override
-    protected void onDestroy() {
-        FlutterWebviewPlugin.channel.invokeMethod("onDestroy", null);
-        super.onDestroy();
-    }
-
-    @Override
-    public void onBackPressed() {
-        if(webViewManager.webView.canGoBack()){
-            webViewManager.webView.goBack();
-            return;
-        }
-        FlutterWebviewPlugin.channel.invokeMethod("onBackPressed", null);
-        super.onBackPressed();
-    }
-
-    @Override
-    public void onMethodCall(MethodCall call, MethodChannel.Result result) {
-        switch (call.method) {
-            case "eval":
-                webViewManager.eval(call, result);
-                break;
-            default:
-                result.notImplemented();
-                break;
-        }
-    }
-}

+ 5 - 0
android/src/main/java/com/flutter_webview_plugin/WebviewManager.java

@@ -9,6 +9,7 @@ import android.webkit.CookieManager;
 import android.webkit.ValueCallback;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
+import android.widget.FrameLayout;
 
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
@@ -89,4 +90,8 @@ class WebviewManager {
             }
         });
     }
+
+    void resize(FrameLayout.LayoutParams params) {
+        webView.setLayoutParams(params);
+    }
 }

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

@@ -8,6 +8,18 @@ import com.flutter_webview_plugin.FlutterWebviewPlugin;
  */
 public final class GeneratedPluginRegistrant {
   public static void registerWith(PluginRegistry registry) {
+    if (alreadyRegisteredWith(registry)) {
+      return;
+    }
     FlutterWebviewPlugin.registerWith(registry.registrarFor("com.flutter_webview_plugin.FlutterWebviewPlugin"));
   }
+
+  private static boolean alreadyRegisteredWith(PluginRegistry registry) {
+    final String key = GeneratedPluginRegistrant.class.getCanonicalName();
+    if (registry.hasPlugin(key)) {
+      return true;
+    }
+    registry.registrarFor(key);
+    return false;
+  }
 }

+ 27 - 7
example/lib/main.dart

@@ -7,6 +7,8 @@ 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";
 
+String selectedUrl = "https://github.com/dart-flitter/flutter_webview_plugin";
+
 void main() {
   runApp(new MyApp());
 }
@@ -19,7 +21,15 @@ class MyApp extends StatelessWidget {
       theme: new ThemeData(
         primarySwatch: Colors.blue,
       ),
-      home: new MyHomePage(title: 'Flutter WebView Demo'),
+      routes: {
+        "/": (_) => new MyHomePage(title: "Flutter WebView Demo"),
+        "/widget": (_) => new WebviewScaffold(
+              url: selectedUrl,
+              appBar: new AppBar(
+                title: new Text("Widget webview"),
+              ),
+            )
+      },
     );
   }
 }
@@ -46,8 +56,7 @@ class _MyHomePageState extends State<MyHomePage> {
   // On urlChanged stream
   StreamSubscription<WebViewStateChanged> _onStateChanged;
 
-  TextEditingController _urlCtrl = new TextEditingController(
-      text: "https://github.com/dart-flitter/flutter_webview_plugin");
+  TextEditingController _urlCtrl = new TextEditingController(text: selectedUrl);
 
   TextEditingController _codeCtrl =
       new TextEditingController(text: "window.navigator.userAgent");
@@ -60,6 +69,12 @@ class _MyHomePageState extends State<MyHomePage> {
   initState() {
     super.initState();
 
+    flutterWebviewPlugin.close();
+
+    _urlCtrl.addListener(() {
+      selectedUrl = _urlCtrl.text;
+    });
+
     // Add a listener to on destroy WebView, so you can make came actions.
     _onDestroy = flutterWebviewPlugin.onDestroy.listen((_) {
       if (mounted) {
@@ -116,8 +131,7 @@ class _MyHomePageState extends State<MyHomePage> {
           ),
           new RaisedButton(
             onPressed: () {
-              flutterWebviewPlugin.launch(_urlCtrl.text,
-                  fullScreen: false,
+              flutterWebviewPlugin.launch(selectedUrl,
                   rect: new Rect.fromLTWH(
                       0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
                   userAgent: kAndroidUserAgent);
@@ -126,16 +140,22 @@ class _MyHomePageState extends State<MyHomePage> {
           ),
           new RaisedButton(
             onPressed: () {
-              flutterWebviewPlugin.launch(_urlCtrl.text, hidden: true);
+              flutterWebviewPlugin.launch(selectedUrl, hidden: true);
             },
             child: new Text("Open 'hidden' Webview"),
           ),
           new RaisedButton(
             onPressed: () {
-              flutterWebviewPlugin.launch(_urlCtrl.text, fullScreen: true);
+              flutterWebviewPlugin.launch(selectedUrl);
             },
             child: new Text("Open Fullscreen Webview"),
           ),
+          new RaisedButton(
+            onPressed: () {
+              Navigator.of(context).pushNamed("/widget");
+            },
+            child: new Text("Open widget webview"),
+          ),
           new Container(
             padding: const EdgeInsets.all(24.0),
             child: new TextField(controller: _codeCtrl),

+ 0 - 1
ios/Classes/FlutterWebviewPlugin.h

@@ -1,5 +1,4 @@
 #import <Flutter/Flutter.h>
-#import "WebviewController.h"
 
 static FlutterMethodChannel *channel;
 

+ 22 - 10
ios/Classes/FlutterWebviewPlugin.m

@@ -31,21 +31,24 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
     if ([@"launch" isEqualToString:call.method]) {
         if (!self.webview)
-            [self initWebView:call];
+            [self initWebview:call];
         else
-            [self launch:call];
+            [self navigate:call];
         result(nil);
     } else if ([@"close" isEqualToString:call.method]) {
         [self closeWebView];
         result(nil);
     } else if ([@"eval" isEqualToString:call.method]) {
         result([self evalJavascript:call]);
+    } else if ([@"resize" isEqualToString:call.method]) {
+        [self resize:call];
+        result(nil);
     } else {
         result(FlutterMethodNotImplemented);
     }
 }
 
-- (void)initWebView:(FlutterMethodCall*)call {
+- (void)initWebview:(FlutterMethodCall*)call {
     // NSNumber *withJavascript = call.arguments[@"withJavascript"];
     NSNumber *clearCache = call.arguments[@"clearCache"];
     NSNumber *clearCookies = call.arguments[@"clearCookies"];
@@ -70,12 +73,8 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     
     CGRect rc;
     if (rect != nil) {
-        rc = CGRectMake([[rect valueForKey:@"left"] doubleValue],
-                        [[rect valueForKey:@"top"] doubleValue],
-                        [[rect valueForKey:@"width"] doubleValue],
-                        [[rect valueForKey:@"height"] doubleValue]);
+        rc = [self parseRect:rect];
     } else {
-        // TODO: create top NavigatorController and push
         rc = self.viewController.view.bounds;
     }
     
@@ -86,10 +85,17 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
         self.webview.hidden = YES;
     [self.viewController.view addSubview:self.webview];
     
-    [self launch:call];
+    [self navigate:call];
+}
+
+- (CGRect)parseRect:(NSDictionary *)rect {
+    return CGRectMake([[rect valueForKey:@"left"] doubleValue],
+                      [[rect valueForKey:@"top"] doubleValue],
+                      [[rect valueForKey:@"width"] doubleValue],
+                      [[rect valueForKey:@"height"] doubleValue]);
 }
 
-- (void)launch:(FlutterMethodCall*)call {
+- (void)navigate:(FlutterMethodCall*)call {
     NSString *url = call.arguments[@"url"];
     
     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
@@ -103,6 +109,12 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     return result;
 }
 
+- (void)resize:(FlutterMethodCall*)call {
+    NSDictionary *rect = call.arguments[@"rect"];
+    CGRect rc = [self parseRect:rect];
+    self.webview.frame = rc;
+}
+
 - (void)closeWebView {
     [self.webview stopLoading];
     [self.webview removeFromSuperview];

+ 0 - 13
ios/Classes/WebviewController.h

@@ -1,13 +0,0 @@
-//
-//  WebviewController.h
-//  Pods
-//
-//  Created by Toufik Zitouni on 6/17/17.
-//
-//
-
-#import <UIKit/UIKit.h>
-
-@interface WebviewController : UIViewController
-- (instancetype)initWithUrl:(NSString *)url withJavascript:(NSNumber *)withJavascript clearCache:(NSNumber *)clearCache clearCookes:(NSNumber *)clearCookies;
-@end

+ 0 - 60
ios/Classes/WebviewController.m

@@ -1,60 +0,0 @@
-//
-//  WebviewController.m
-//  Pods
-//
-//  Created by Toufik Zitouni on 6/17/17.
-//
-//
-
-#import "WebviewController.h"
-#import "FlutterWebviewPlugin.h"
-
-@interface WebviewController ()
-@property (nonatomic, retain) NSString *url;
-@property NSNumber *withJavascript;
-@property NSNumber *clearCache;
-@property NSNumber *clearCookies;
-@end
-
-@implementation WebviewController
-
-- (instancetype)initWithUrl:(NSString *)url withJavascript:(NSNumber *)withJavascript clearCache:(NSNumber *)clearCache clearCookes:(NSNumber *)clearCookies {
-    self = [super init];
-    if (self) {
-        self.url = url;
-        self.withJavascript = withJavascript;
-        self.clearCache = clearCache;
-        self.clearCookies = clearCookies;
-    }
-    return self;
-}
-
-- (void)viewDidLoad {
-    [super viewDidLoad];
-    UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(backButtonPressed:)];
-    self.navigationItem.leftBarButtonItem = backButton;
-    
-    UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.frame];
-    
-    if ([self.clearCache boolValue]) {
-        [[NSURLCache sharedURLCache] removeAllCachedResponses];
-    }
-    
-    if ([self.clearCookies boolValue]) {
-        [[NSURLSession sharedSession] resetWithCompletionHandler:^{
-            
-        }];
-    }
-    
-    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.url]]];
-    
-    webView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
-    [self.view addSubview:webView];
-}
-
-- (IBAction)backButtonPressed:(id)sender {
-    [channel invokeMethod:@"onBackPressed" arguments:nil];
-    [self dismissViewControllerAnimated:YES completion:nil];
-}
-
-@end

+ 3 - 167
lib/flutter_webview_plugin.dart

@@ -1,168 +1,4 @@
-import 'dart:async';
+library flutter_webview_plugin;
 
-import 'package:flutter/services.dart';
-import 'package:flutter/material.dart';
-
-const _kChannel = 'flutter_webview_plugin';
-
-// TODO: more general state for iOS/android
-enum WebViewState { shouldStart, startLoad, finishLoad }
-
-// TODO: use an id by webview to be able to manage multiple webview
-
-/// Singleton Class that communicate with a Webview Instance
-/// Have to be instanciate after `runApp` called.
-class FlutterWebviewPlugin {
-  final _channel = const MethodChannel(_kChannel);
-
-  final _onDestroy = new StreamController<Null>.broadcast();
-  final _onBackPressed = new StreamController<Null>.broadcast();
-  final _onUrlChanged = new StreamController<String>.broadcast();
-  final _onStateChanged = new StreamController<WebViewStateChanged>.broadcast();
-  final _onError = new StreamController<String>.broadcast();
-
-  static FlutterWebviewPlugin _instance;
-
-  factory FlutterWebviewPlugin() => _instance ??= new FlutterWebviewPlugin._();
-
-  FlutterWebviewPlugin._() {
-    _channel.setMethodCallHandler(_handleMessages);
-  }
-
-  Future<Null> _handleMessages(MethodCall call) async {
-    switch (call.method) {
-      case "onDestroy":
-        _onDestroy.add(null);
-        break;
-      case "onBackPressed":
-        _onBackPressed.add(null);
-        break;
-      case "onUrlChanged":
-        _onUrlChanged.add(call.arguments["url"]);
-        break;
-      case "onState":
-        _onStateChanged.add(new WebViewStateChanged.fromMap(call.arguments));
-        break;
-      case "onError":
-        _onError.add(call.arguments);
-        break;
-    }
-  }
-
-  /// Listening the OnDestroy LifeCycle Event for Android
-  Stream<Null> get onDestroy => _onDestroy.stream;
-
-  /// Listening url changed
-  Stream<String> 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(iOS)|startLoad|finishLoad}
-  /// more detail than other events
-  Stream<WebViewStateChanged> get onStateChanged => _onStateChanged.stream;
-
-  /// Start the Webview with [url]
-  /// - [withJavascript] enable Javascript or not for the Webview
-  ///     iOS WebView: Not implemented yet
-  /// - [clearCache] clear the cache of the Webview
-  ///     iOS WebView: Not implemented yet
-  /// - [clearCookies] clear all cookies of the Webview
-  ///     iOS WebView: Not implemented yet
-  /// - [hidden] not show
-  /// - [fullScreen]: show in full screen mode, default true
-  /// - [rect]: show in rect(not full screen)
-  /// - [enableAppScheme]: false will enable all schemes, true only for httt/https/about
-  ///     android: Not implemented yet
-  /// - [userAgent]: set the User-Agent of WebView
-  Future<Null> launch(String url,
-      {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);
-  }
-
-  /// Execute Javascript inside webview
-  Future<String> evalJavascript(String code) =>
-      _channel.invokeMethod('eval', {"code": code});
-
-  /// Close the Webview
-  /// Will trigger the [onDestroy] event
-  Future<Null> close() => _channel.invokeMethod("close");
-
-  /// Close all Streams
-  void dispose() {
-    _onDestroy.close();
-    _onBackPressed.close();
-    _onUrlChanged.close();
-    _onStateChanged.close();
-    _onError.close();
-    _instance = null;
-  }
-
-  Future<Map<String, dynamic>> getCookies() async {
-    final cookiesString = await evalJavascript("document.cookie");
-    final cookies = {};
-
-    if (cookiesString?.isNotEmpty == true) {
-      cookiesString.split(";").forEach((String cookie) {
-        final splited = cookie.split("=");
-        cookies[splited[0]] = splited[1];
-      });
-    }
-
-    return cookies;
-  }
-}
-
-class WebViewStateChanged {
-  final WebViewState type;
-  final String url;
-  final int navigationType;
-
-  WebViewStateChanged(this.type, this.url, this.navigationType);
-
-  factory WebViewStateChanged.fromMap(Map<String, dynamic> map) {
-    WebViewState t;
-    switch (map["type"]) {
-      case "shouldStart":
-        t = WebViewState.shouldStart;
-        break;
-      case "startLoad":
-        t = WebViewState.startLoad;
-        break;
-      case "finishLoad":
-        t = WebViewState.finishLoad;
-        break;
-    }
-    return new WebViewStateChanged(t, map["url"], map["navigationType"]);
-  }
-}
+export 'src/base.dart';
+export 'src/webview_scaffold.dart';

+ 166 - 0
lib/src/base.dart

@@ -0,0 +1,166 @@
+import 'dart:async';
+import 'dart:ui';
+
+import 'package:flutter/services.dart';
+import 'package:flutter/material.dart';
+
+const _kChannel = 'flutter_webview_plugin';
+
+// TODO: more general state for iOS/android
+enum WebViewState { shouldStart, startLoad, finishLoad }
+
+// TODO: use an id by webview to be able to manage multiple webview
+
+/// Singleton Class that communicate with a Webview Instance
+/// Have to be instanciate after `runApp` called.
+class FlutterWebviewPlugin {
+  final _channel = const MethodChannel(_kChannel);
+
+  final _onDestroy = new StreamController<Null>.broadcast();
+  final _onUrlChanged = new StreamController<String>.broadcast();
+  final _onStateChanged = new StreamController<WebViewStateChanged>.broadcast();
+  final _onError = new StreamController<String>.broadcast();
+
+  static FlutterWebviewPlugin _instance;
+
+  factory FlutterWebviewPlugin() => _instance ??= new FlutterWebviewPlugin._();
+
+  FlutterWebviewPlugin._() {
+    _channel.setMethodCallHandler(_handleMessages);
+  }
+
+  Future<Null> _handleMessages(MethodCall call) async {
+    switch (call.method) {
+      case "onDestroy":
+        _onDestroy.add(null);
+        break;
+      case "onUrlChanged":
+        _onUrlChanged.add(call.arguments["url"]);
+        break;
+      case "onState":
+        _onStateChanged.add(new WebViewStateChanged.fromMap(call.arguments));
+        break;
+      case "onError":
+        _onError.add(call.arguments);
+        break;
+    }
+  }
+
+  /// Listening the OnDestroy LifeCycle Event for Android
+  Stream<Null> get onDestroy => _onDestroy.stream;
+
+  /// Listening url changed
+  Stream<String> get onUrlChanged => _onUrlChanged.stream;
+
+  /// Listening the onState Event for iOS WebView and Android
+  /// content is Map for type: {shouldStart(iOS)|startLoad|finishLoad}
+  /// more detail than other events
+  Stream<WebViewStateChanged> get onStateChanged => _onStateChanged.stream;
+
+  /// Start the Webview with [url]
+  /// - [withJavascript] enable Javascript or not for the Webview
+  ///     iOS WebView: Not implemented yet
+  /// - [clearCache] clear the cache of the Webview
+  /// - [clearCookies] clear all cookies of the Webview
+  /// - [hidden] not show
+  /// - [rect]: show in rect, fullscreen if null
+  /// - [enableAppScheme]: false will enable all schemes, true only for httt/https/about
+  ///     android: Not implemented yet
+  /// - [userAgent]: set the User-Agent of WebView
+  /// - [title]: title for app/navigation bar
+  /// - [withBackButton]: show back button when fullscreen
+  Future<Null> launch(String url,
+      {bool withJavascript,
+      bool clearCache,
+      bool clearCookies,
+      bool hidden,
+      bool enableAppScheme,
+      Rect rect,
+      String userAgent}) async {
+    Map<String, dynamic> args = {
+      "url": url,
+      "withJavascript": withJavascript ?? true,
+      "clearCache": clearCache ?? false,
+      "hidden": hidden ?? false,
+      "clearCookies": clearCookies ?? false,
+      "enableAppScheme": enableAppScheme ?? true,
+      "userAgent": userAgent,
+    };
+    if (rect != null) {
+      args["rect"] = {
+        "left": rect.left,
+        "top": rect.top,
+        "width": rect.width,
+        "height": rect.height
+      };
+    }
+    await _channel.invokeMethod('launch', args);
+  }
+
+  /// Execute Javascript inside webview
+  Future<String> evalJavascript(String code) =>
+      _channel.invokeMethod('eval', {"code": code});
+
+  /// Close the Webview
+  /// Will trigger the [onDestroy] event
+  Future<Null> close() => _channel.invokeMethod("close");
+
+  /// Close all Streams
+  void dispose() {
+    _onDestroy.close();
+    _onUrlChanged.close();
+    _onStateChanged.close();
+    _onError.close();
+    _instance = null;
+  }
+
+  Future<Map<String, dynamic>> getCookies() async {
+    final cookiesString = await evalJavascript("document.cookie");
+    final cookies = {};
+
+    if (cookiesString?.isNotEmpty == true) {
+      cookiesString.split(";").forEach((String cookie) {
+        final splited = cookie.split("=");
+        cookies[splited[0]] = splited[1];
+      });
+    }
+
+    return cookies;
+  }
+
+  /// resize webview
+  Future<Null> resize(Rect rect) async {
+    final args = {};
+    args["rect"] = {
+      "left": rect.left,
+      "top": rect.top,
+      "width": rect.width,
+      "height": rect.height
+    };
+    await _channel.invokeMethod('resize', args);
+  }
+}
+
+class WebViewStateChanged {
+  final WebViewState type;
+  final String url;
+  final int navigationType;
+
+  WebViewStateChanged(this.type, this.url, this.navigationType);
+
+  factory WebViewStateChanged.fromMap(Map<String, dynamic> map) {
+    WebViewState t;
+    switch (map["type"]) {
+      case "shouldStart":
+        t = WebViewState.shouldStart;
+        break;
+      case "startLoad":
+        t = WebViewState.startLoad;
+        break;
+      case "finishLoad":
+        t = WebViewState.finishLoad;
+        break;
+    }
+    return new WebViewStateChanged(t, map["url"], map["navigationType"]);
+  }
+}

+ 87 - 0
lib/src/webview_scaffold.dart

@@ -0,0 +1,87 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:meta/meta.dart';
+
+import 'base.dart';
+
+class WebviewScaffold extends StatefulWidget {
+  final PreferredSizeWidget appBar;
+  final String url;
+  final withJavascript;
+  final clearCache;
+  final clearCookies;
+  final enableAppScheme;
+  final userAgent;
+  final primary;
+
+  WebviewScaffold(
+      {Key key,
+      this.appBar,
+      @required this.url,
+      this.withJavascript,
+      this.clearCache,
+      this.clearCookies,
+      this.enableAppScheme,
+      this.userAgent,
+      this.primary: true})
+      : super(key: key);
+
+  @override
+  _WebviewScaffoldState createState() => new _WebviewScaffoldState();
+}
+
+class _WebviewScaffoldState extends State<WebviewScaffold> {
+  final webviewReference = new FlutterWebviewPlugin();
+  Rect _rect;
+  Timer _resizeTimer;
+
+  void initState() {
+    super.initState();
+    webviewReference.close();
+  }
+
+  void dispose() {
+    super.dispose();
+    webviewReference.close();
+    webviewReference.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (_rect == null) {
+      _rect = _buildRect(context);
+      webviewReference.launch(widget.url,
+          withJavascript: widget.withJavascript,
+          clearCache: widget.clearCache,
+          clearCookies: widget.clearCookies,
+          enableAppScheme: widget.enableAppScheme,
+          userAgent: widget.userAgent,
+          rect: _rect);
+    } else {
+      Rect rect = _buildRect(context);
+      if (_rect != rect) {
+        _rect = rect;
+        _resizeTimer?.cancel();
+        _resizeTimer = new Timer(new Duration(milliseconds: 300), () {
+          // avoid resizing to fast when build is called multiple time
+          webviewReference.resize(_rect);
+        });
+      }
+    }
+    return new Scaffold(
+        appBar: widget.appBar,
+        body: new Center(child: new CircularProgressIndicator()));
+  }
+
+  Rect _buildRect(BuildContext context) {
+    bool fullscreen = widget.appBar == null;
+
+    final mediaQuery = MediaQuery.of(context);
+    final topPadding = widget.primary ? mediaQuery.padding.top : 0.0;
+    final appBarHeight =
+        fullscreen ? 0.0 : widget.appBar.preferredSize.height + topPadding;
+    return new Rect.fromLTWH(0.0, appBarHeight, mediaQuery.size.width,
+        mediaQuery.size.height - appBarHeight);
+  }
+}

+ 1 - 1
pubspec.yaml

@@ -5,7 +5,7 @@ authors:
 - Toufik Zitouni <toufiksapps@gmail.com>
 - Pedia <kpedia@163.com>
 homepage: https://github.com/dart-flitter/flutter_webview_plugin
-version: 0.0.10
+version: 0.1.0
 
 flutter:
   plugin: