Bläddra i källkod

Merge pull request #1 from fluttercommunity/master

Synchronize my fork to original repository
HungHD 7 år sedan
förälder
incheckning
d86ac7ee23

+ 1 - 0
.gitignore

@@ -1,6 +1,7 @@
 .DS_Store
 .atom/
 .idea
+.vscode
 .packages
 .pub/
 build/

+ 17 - 2
CHANGELOG.md

@@ -1,3 +1,18 @@
+# 0.3.0
+
+- Fixes rect capture issue. Ensures WebView remains in the correct place on screen even when keyboard appears.
+- Fixed iOS crash issue with Flutter `>= 0.10.2`.
+- Added new `clearCookies` feature.
+- Added support for `hidden` and `initialChild` feature to show page loading view.
+- Added supportMultipleWindows: enables Multiple Window Support on Android.
+- Added appCacheEnabled: enables Application Caches API on Android.
+- Added allowFileURLs: allows `file://` local file URLs.
+- iOS Now supports: `reload`, `goBack`, and `goForward`.
+- iOS Bug fix `didFailNavigation` #77
+- Updated Android `compileSdkVersion` to `27` matching offical Flutter plugins.
+- Fixed Android `reloadUrl` so settings are not cleared.
+- Enabled compatible `Mixed Content Mode` on Android.
+
 # 0.2.1
 
 - Added webview scrolling listener
@@ -57,10 +72,10 @@
     - state change event
     - embed in rectangle or fullscreen if null
     - hidden webview
-    
+
 - Android
     - adding Activity in manifest is not needed anymore
-    
+
 - Add `WebviewScaffold`
 
 # 0.0.9

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 1
README.md


+ 5 - 4
android/build.gradle

@@ -1,4 +1,4 @@
-group 'com.yourcompany.flutter_webview_plugin'
+group 'com.flutter_webview_plugin'
 version '1.0-SNAPSHOT'
 
 buildscript {
@@ -8,7 +8,7 @@ buildscript {
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.1.2'
+        classpath 'com.android.tools.build:gradle:3.2.1'
     }
 }
 
@@ -21,11 +21,12 @@ allprojects {
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 25
-    buildToolsVersion '27.0.3'
+    compileSdkVersion 27
 
     defaultConfig {
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        // NOTE(jeffmikels): When targetSdkVersion or minSdkVersion is not set or < 4, gradle adds 
+        // additional scary permissions such as WRITE_EXTERNAL_STORAGE and READ_PHONE_STATE.
         minSdkVersion 16
     }
     lintOptions {

+ 0 - 1
android/src/main/AndroidManifest.xml

@@ -1,4 +1,3 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.flutter_webview_plugin">
-
 </manifest>

+ 45 - 24
android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java

@@ -7,6 +7,9 @@ import android.content.Intent;
 import android.graphics.Point;
 import android.view.Display;
 import android.widget.FrameLayout;
+import android.webkit.CookieManager;
+import android.webkit.ValueCallback;
+import android.os.Build;
 
 import java.util.Map;
 
@@ -70,7 +73,10 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
                 break;
             case "stopLoading":
                 stopLoading(call, result);
-                break;				
+                break;
+            case "cleanCookies":
+                cleanCookies(call, result);
+                break;
             default:
                 result.notImplemented();
                 break;
@@ -86,8 +92,11 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
         boolean clearCookies = call.argument("clearCookies");
         boolean withZoom = call.argument("withZoom");
         boolean withLocalStorage = call.argument("withLocalStorage");
+        boolean supportMultipleWindows = call.argument("supportMultipleWindows");
+        boolean appCacheEnabled = call.argument("appCacheEnabled");
         Map<String, String> headers = call.argument("headers");
         boolean scrollBar = call.argument("scrollBar");
+        boolean allowFileURLs = call.argument("allowFileURLs");
 
         if (webViewManager == null || webViewManager.closed == true) {
             webViewManager = new WebviewManager(activity);
@@ -106,7 +115,10 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
                 headers,
                 withZoom,
                 withLocalStorage,
-                scrollBar
+                scrollBar,
+                supportMultipleWindows,
+                appCacheEnabled,
+                allowFileURLs
         );
         result.success(null);
     }
@@ -132,7 +144,7 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
     }
 
     private void stopLoading(MethodCall call, MethodChannel.Result result) {
-        if (webViewManager != null){
+        if (webViewManager != null) {
             webViewManager.stopLoading(call, result);
         }
     }
@@ -144,47 +156,40 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
         }
     }
 
-    /** 
-    * Navigates back on the Webview.
-    */
+    /**
+     * Navigates back on the Webview.
+     */
     private void back(MethodCall call, MethodChannel.Result result) {
         if (webViewManager != null) {
             webViewManager.back(call, result);
         }
     }
-    /** 
-    * Navigates forward on the Webview.
-    */
+
+    /**
+     * Navigates forward on the Webview.
+     */
     private void forward(MethodCall call, MethodChannel.Result result) {
         if (webViewManager != null) {
             webViewManager.forward(call, result);
         }
     }
 
-    /** 
-    * Reloads the Webview.
-    */
+    /**
+     * Reloads the Webview.
+     */
     private void reload(MethodCall call, MethodChannel.Result result) {
         if (webViewManager != null) {
             webViewManager.reload(call, result);
         }
     }
+
     private void reloadUrl(MethodCall call, MethodChannel.Result result) {
         if (webViewManager != null) {
             String url = call.argument("url");
-            webViewManager.openUrl(false,
-                    false,
-                    false,
-                    false,
-                    "",
-                    url,
-                    null,
-                    false,
-                    false,
-                    false
-            );
+            webViewManager.reloadUrl(url);
         }
     }
+
     private void eval(MethodCall call, final MethodChannel.Result result) {
         if (webViewManager != null) {
             webViewManager.eval(call, result);
@@ -198,17 +203,33 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
         }
         result.success(null);
     }
+
     private void hide(MethodCall call, final MethodChannel.Result result) {
         if (webViewManager != null) {
             webViewManager.hide(call, result);
         }
     }
+
     private void show(MethodCall call, final MethodChannel.Result result) {
         if (webViewManager != null) {
             webViewManager.show(call, result);
         }
     }
 
+    private void cleanCookies(MethodCall call, final MethodChannel.Result result) {
+        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();
+        }
+        result.success(null);
+    }
+
     private int dp2px(Context context, float dp) {
         final float scale = context.getResources().getDisplayMetrics().density;
         return (int) (dp * scale + 0.5f);
@@ -216,7 +237,7 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
 
     @Override
     public boolean onActivityResult(int i, int i1, Intent intent) {
-        if(webViewManager != null && webViewManager.resultHandler != null){
+        if (webViewManager != null && webViewManager.resultHandler != null) {
             return webViewManager.resultHandler.handleResult(i, i1, intent);
         }
         return false;

+ 54 - 22
android/src/main/java/com/flutter_webview_plugin/WebviewManager.java

@@ -12,6 +12,7 @@ import android.view.ViewGroup;
 import android.webkit.CookieManager;
 import android.webkit.ValueCallback;
 import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.FrameLayout;
@@ -39,26 +40,27 @@ class WebviewManager {
         public boolean handleResult(int requestCode, int resultCode, Intent intent){
             boolean handled = false;
             if(Build.VERSION.SDK_INT >= 21){
-                Uri[] results = null;
-                // check result
-                if(resultCode == Activity.RESULT_OK){
-                    if(requestCode == FILECHOOSER_RESULTCODE){
-                        if(mUploadMessageArray != null){
-                            String dataString = intent.getDataString();
-                            if(dataString != null){
-                                results = new Uri[]{Uri.parse(dataString)};
-                            }
+                if(requestCode == FILECHOOSER_RESULTCODE){
+                    Uri[] results = null;
+                    if(resultCode == Activity.RESULT_OK && intent != null){
+                        String dataString = intent.getDataString();
+                        if(dataString != null){
+                            results = new Uri[]{ Uri.parse(dataString) };
                         }
-                        handled = true;
                     }
+                    if(mUploadMessageArray != null){
+                        mUploadMessageArray.onReceiveValue(results);
+                        mUploadMessageArray = null;
+                    }
+                    handled = true;
                 }
-                mUploadMessageArray.onReceiveValue(results);
-                mUploadMessageArray = null;
             }else {
                 if (requestCode == FILECHOOSER_RESULTCODE) {
-                    if (null != mUploadMessage) {
-                        Uri result = intent == null || resultCode != RESULT_OK ? null
-                                : intent.getData();
+                	Uri result = null;
+                    if (resultCode == RESULT_OK && intent != null) {
+                        result = intent.getData();
+                    }
+                    if (mUploadMessage != null) {
                         mUploadMessage.onReceiveValue(result);
                         mUploadMessage = null;
                     }
@@ -189,11 +191,37 @@ class WebviewManager {
         webView.clearFormData();
     }
 
-    void openUrl(boolean withJavascript, boolean clearCache, boolean hidden, boolean clearCookies, String userAgent, String url, Map<String, String> headers, boolean withZoom, boolean withLocalStorage, boolean scrollBar) {
+    void openUrl(
+            boolean withJavascript,
+            boolean clearCache,
+            boolean hidden,
+            boolean clearCookies,
+            String userAgent,
+            String url,
+            Map<String, String> headers,
+            boolean withZoom,
+            boolean withLocalStorage,
+            boolean scrollBar,
+            boolean supportMultipleWindows,
+            boolean appCacheEnabled,
+            boolean allowFileURLs
+    ) {
         webView.getSettings().setJavaScriptEnabled(withJavascript);
         webView.getSettings().setBuiltInZoomControls(withZoom);
         webView.getSettings().setSupportZoom(withZoom);
         webView.getSettings().setDomStorageEnabled(withLocalStorage);
+        webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(supportMultipleWindows);
+
+        webView.getSettings().setSupportMultipleWindows(supportMultipleWindows);
+
+        webView.getSettings().setAppCacheEnabled(appCacheEnabled);
+
+        webView.getSettings().setAllowFileAccessFromFileURLs(allowFileURLs);
+        webView.getSettings().setAllowUniversalAccessFromFileURLs(allowFileURLs);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
+        }
 
         if (clearCache) {
             clearCache();
@@ -210,7 +238,7 @@ class WebviewManager {
         if (userAgent != null) {
             webView.getSettings().setUserAgentString(userAgent);
         }
-      
+
         if(!scrollBar){
             webView.setVerticalScrollBarEnabled(false);
         }
@@ -222,6 +250,10 @@ class WebviewManager {
         }
     }
 
+    void reloadUrl(String url) {
+        webView.loadUrl(url);
+    }
+
     void close(MethodCall call, MethodChannel.Result result) {
         if (webView != null) {
             ViewGroup vg = (ViewGroup) (webView.getParent());
@@ -251,7 +283,7 @@ class WebviewManager {
             }
         });
     }
-    /** 
+    /**
     * Reloads the Webview.
     */
     void reload(MethodCall call, MethodChannel.Result result) {
@@ -259,7 +291,7 @@ class WebviewManager {
             webView.reload();
         }
     }
-    /** 
+    /**
     * Navigates back on the Webview.
     */
     void back(MethodCall call, MethodChannel.Result result) {
@@ -267,7 +299,7 @@ class WebviewManager {
             webView.goBack();
         }
     }
-    /** 
+    /**
     * Navigates forward on the Webview.
     */
     void forward(MethodCall call, MethodChannel.Result result) {
@@ -279,13 +311,13 @@ class WebviewManager {
     void resize(FrameLayout.LayoutParams params) {
         webView.setLayoutParams(params);
     }
-    /** 
+    /**
     * Checks if going back on the Webview is possible.
     */
     boolean canGoBack() {
         return webView.canGoBack();
     }
-    /** 
+    /**
     * Checks if going forward on the Webview is possible.
     */
     boolean canGoForward() {

+ 3 - 5
example/android/app/build.gradle

@@ -16,7 +16,6 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
 
 android {
     compileSdkVersion 27
-    buildToolsVersion '27.0.3'
 
     lintOptions {
         disable 'InvalidPackage'
@@ -24,7 +23,6 @@ android {
 
     defaultConfig {
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-        
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
         applicationId "com.yourcompany.flutter_webview_plugin_example"
     }
@@ -43,7 +41,7 @@ flutter {
 }
 
 dependencies {
-    androidTestCompile 'com.android.support:support-annotations:25.0.0'
-    androidTestCompile 'com.android.support.test:runner:0.5'
-    androidTestCompile 'com.android.support.test:rules:0.5'
+    androidTestImplementation 'com.android.support:support-annotations:27.0.0'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test:rules:1.0.2'
 }

+ 0 - 2
example/android/app/src/main/AndroidManifest.xml

@@ -3,8 +3,6 @@
     android:versionCode="1"
     android:versionName="0.0.1">
 
-    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
-
     <!-- The INTERNET permission is required for development. Specifically,
          flutter needs it to communicate with the running application
          to allow setting breakpoints, to provide hot reload, etc.

+ 1 - 1
example/android/build.gradle

@@ -8,7 +8,7 @@ buildscript {
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.1.1'
+        classpath 'com.android.tools.build:gradle:3.2.1'
     }
 }
 

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 432 - 314
example/ios/Flutter/flutter_assets/LICENSE


+ 1 - 1
example/ios/Runner.xcodeproj/project.pbxproj

@@ -242,7 +242,7 @@
 			);
 			inputPaths = (
 				"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
-				"${PODS_ROOT}/../../../../../development/flutter/bin/cache/artifacts/engine/ios/Flutter.framework",
+				"${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";

+ 133 - 104
example/lib/main.dart

@@ -9,28 +9,61 @@ const kAndroidUserAgent =
 
 String selectedUrl = 'https://flutter.io';
 
-void main() {
-  runApp(new MyApp());
-}
+void main() => runApp(MyApp());
 
 class MyApp extends StatelessWidget {
+  final flutterWebViewPlugin = FlutterWebviewPlugin();
+
   @override
   Widget build(BuildContext context) {
-    return new MaterialApp(
+    return MaterialApp(
       title: 'Flutter WebView Demo',
-      theme: new ThemeData(
+      theme: ThemeData(
         primarySwatch: Colors.blue,
       ),
       routes: {
         '/': (_) => const MyHomePage(title: 'Flutter WebView Demo'),
-        '/widget': (_) => new WebviewScaffold(
-              url: selectedUrl,
-              appBar: new AppBar(
-                title: const Text('Widget webview'),
+        '/widget': (_) {
+          return WebviewScaffold(
+            url: selectedUrl,
+            appBar: AppBar(
+              title: const Text('Widget WebView'),
+            ),
+            withZoom: true,
+            withLocalStorage: true,
+            hidden: true,
+            initialChild: Container(
+              color: Colors.redAccent,
+              child: const Center(
+                child: Text('Waiting.....'),
+              ),
+            ),
+            bottomNavigationBar: BottomAppBar(
+              child: Row(
+                children: <Widget>[
+                  IconButton(
+                    icon: const Icon(Icons.arrow_back_ios),
+                    onPressed: () {
+                      flutterWebViewPlugin.goBack();
+                    },
+                  ),
+                  IconButton(
+                    icon: const Icon(Icons.arrow_forward_ios),
+                    onPressed: () {
+                      flutterWebViewPlugin.goForward();
+                    },
+                  ),
+                  IconButton(
+                    icon: const Icon(Icons.autorenew),
+                    onPressed: () {
+                      flutterWebViewPlugin.reload();
+                    },
+                  ),
+                ],
               ),
-              withZoom: true,
-              withLocalStorage: true,
-            )
+            ),
+          );
+        },
       },
     );
   }
@@ -42,12 +75,12 @@ class MyHomePage extends StatefulWidget {
   final String title;
 
   @override
-  _MyHomePageState createState() => new _MyHomePageState();
+  _MyHomePageState createState() => _MyHomePageState();
 }
 
 class _MyHomePageState extends State<MyHomePage> {
   // Instance of WebView plugin
-  final flutterWebviewPlugin = new FlutterWebviewPlugin();
+  final flutterWebViewPlugin = FlutterWebviewPlugin();
 
   // On destroy stream
   StreamSubscription _onDestroy;
@@ -64,12 +97,11 @@ class _MyHomePageState extends State<MyHomePage> {
 
   StreamSubscription<double> _onScrollXChanged;
 
-  final _urlCtrl = new TextEditingController(text: selectedUrl);
+  final _urlCtrl = TextEditingController(text: selectedUrl);
 
-  final _codeCtrl =
-      new TextEditingController(text: 'window.navigator.userAgent');
+  final _codeCtrl = TextEditingController(text: 'window.navigator.userAgent');
 
-  final _scaffoldKey = new GlobalKey<ScaffoldState>();
+  final _scaffoldKey = GlobalKey<ScaffoldState>();
 
   final _history = [];
 
@@ -77,23 +109,22 @@ class _MyHomePageState extends State<MyHomePage> {
   void initState() {
     super.initState();
 
-    flutterWebviewPlugin.close();
+    flutterWebViewPlugin.close();
 
     _urlCtrl.addListener(() {
       selectedUrl = _urlCtrl.text;
     });
 
     // Add a listener to on destroy WebView, so you can make came actions.
-    _onDestroy = flutterWebviewPlugin.onDestroy.listen((_) {
+    _onDestroy = flutterWebViewPlugin.onDestroy.listen((_) {
       if (mounted) {
         // Actions like show a info toast.
-        _scaffoldKey.currentState.showSnackBar(
-            const SnackBar(content: const Text('Webview Destroyed')));
+        _scaffoldKey.currentState.showSnackBar(const SnackBar(content: const Text('Webview Destroyed')));
       }
     });
 
     // Add a listener to on url changed
-    _onUrlChanged = flutterWebviewPlugin.onUrlChanged.listen((String url) {
+    _onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((String url) {
       if (mounted) {
         setState(() {
           _history.add('onUrlChanged: $url');
@@ -101,26 +132,23 @@ class _MyHomePageState extends State<MyHomePage> {
       }
     });
 
-    _onScrollYChanged =
-        flutterWebviewPlugin.onScrollYChanged.listen((double y) {
+    _onScrollYChanged = flutterWebViewPlugin.onScrollYChanged.listen((double y) {
       if (mounted) {
         setState(() {
-          _history.add("Scroll in  Y Direction: $y");
+          _history.add('Scroll in Y Direction: $y');
         });
       }
     });
 
-    _onScrollXChanged =
-        flutterWebviewPlugin.onScrollXChanged.listen((double x) {
+    _onScrollXChanged = flutterWebViewPlugin.onScrollXChanged.listen((double x) {
       if (mounted) {
         setState(() {
-          _history.add("Scroll in  X Direction: $x");
+          _history.add('Scroll in X Direction: $x');
         });
       }
     });
 
-    _onStateChanged =
-        flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged state) {
+    _onStateChanged = flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) {
       if (mounted) {
         setState(() {
           _history.add('onStateChanged: ${state.type} ${state.url}');
@@ -128,8 +156,7 @@ class _MyHomePageState extends State<MyHomePage> {
       }
     });
 
-    _onHttpError =
-        flutterWebviewPlugin.onHttpError.listen((WebViewHttpError error) {
+    _onHttpError = flutterWebViewPlugin.onHttpError.listen((WebViewHttpError error) {
       if (mounted) {
         setState(() {
           _history.add('onHttpError: ${error.code} ${error.url}');
@@ -148,89 +175,91 @@ class _MyHomePageState extends State<MyHomePage> {
     _onScrollXChanged.cancel();
     _onScrollYChanged.cancel();
 
-    flutterWebviewPlugin.dispose();
+    flutterWebViewPlugin.dispose();
 
     super.dispose();
   }
 
   @override
   Widget build(BuildContext context) {
-    return new Scaffold(
+    return Scaffold(
       key: _scaffoldKey,
-      appBar: new AppBar(
+      appBar: AppBar(
         title: const Text('Plugin example app'),
       ),
-      body: new Column(
-        mainAxisAlignment: MainAxisAlignment.center,
-        children: [
-          new Container(
-            padding: const EdgeInsets.all(24.0),
-            child: new TextField(controller: _urlCtrl),
-          ),
-          new RaisedButton(
-            onPressed: () {
-              flutterWebviewPlugin.launch(selectedUrl,
-                  rect: new Rect.fromLTWH(
-                      0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
-                  userAgent: kAndroidUserAgent);
-            },
-            child: const Text('Open Webview (rect)'),
-          ),
-          new RaisedButton(
-            onPressed: () {
-              flutterWebviewPlugin.launch(selectedUrl, hidden: true);
-            },
-            child: const Text('Open "hidden" Webview'),
-          ),
-          new RaisedButton(
-            onPressed: () {
-              flutterWebviewPlugin.launch(selectedUrl);
-            },
-            child: const Text('Open Fullscreen Webview'),
-          ),
-          new RaisedButton(
-            onPressed: () {
-              Navigator.of(context).pushNamed('/widget');
-            },
-            child: const Text('Open widget webview'),
-          ),
-          new Container(
-            padding: const EdgeInsets.all(24.0),
-            child: new TextField(controller: _codeCtrl),
-          ),
-          new RaisedButton(
-            onPressed: () {
-              final future =
-                  flutterWebviewPlugin.evalJavascript(_codeCtrl.text);
-              future.then((String result) {
-                setState(() {
-                  _history.add('eval: $result');
+      body: SingleChildScrollView(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            Container(
+              padding: const EdgeInsets.all(24.0),
+              child: TextField(controller: _urlCtrl),
+            ),
+            RaisedButton(
+              onPressed: () {
+                flutterWebViewPlugin.launch(
+                  selectedUrl,
+                  rect: Rect.fromLTWH(0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
+                  userAgent: kAndroidUserAgent,
+                );
+              },
+              child: const Text('Open Webview (rect)'),
+            ),
+            RaisedButton(
+              onPressed: () {
+                flutterWebViewPlugin.launch(selectedUrl, hidden: true);
+              },
+              child: const Text('Open "hidden" Webview'),
+            ),
+            RaisedButton(
+              onPressed: () {
+                flutterWebViewPlugin.launch(selectedUrl);
+              },
+              child: const Text('Open Fullscreen Webview'),
+            ),
+            RaisedButton(
+              onPressed: () {
+                Navigator.of(context).pushNamed('/widget');
+              },
+              child: const Text('Open widget webview'),
+            ),
+            Container(
+              padding: const EdgeInsets.all(24.0),
+              child: TextField(controller: _codeCtrl),
+            ),
+            RaisedButton(
+              onPressed: () {
+                final future = flutterWebViewPlugin.evalJavascript(_codeCtrl.text);
+                future.then((String result) {
+                  setState(() {
+                    _history.add('eval: $result');
+                  });
                 });
-              });
-            },
-            child: const Text('Eval some javascript'),
-          ),
-          new RaisedButton(
-            onPressed: () {
-              setState(() {
-                _history.clear();
-              });
-              flutterWebviewPlugin.close();
-            },
-            child: const Text('Close'),
-          ),
-          new RaisedButton(
-            onPressed: () {
-              flutterWebviewPlugin.getCookies().then((m) {
+              },
+              child: const Text('Eval some javascript'),
+            ),
+            RaisedButton(
+              onPressed: () {
                 setState(() {
-                  _history.add('cookies: $m');
+                  _history.clear();
+                });
+                flutterWebViewPlugin.close();
+              },
+              child: const Text('Close'),
+            ),
+            RaisedButton(
+              onPressed: () {
+                flutterWebViewPlugin.getCookies().then((m) {
+                  setState(() {
+                    _history.add('cookies: $m');
+                  });
                 });
-              });
-            },
-            child: const Text('Cookies'),
-          ),
-          new Text(_history.join('\n'))
-        ],
+              },
+              child: const Text('Cookies'),
+            ),
+            Text(_history.join('\n'))
+          ],
+        ),
       ),
     );
   }

+ 44 - 18
ios/Classes/FlutterWebviewPlugin.m

@@ -14,10 +14,10 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     channel = [FlutterMethodChannel
                methodChannelWithName:CHANNEL_NAME
                binaryMessenger:[registrar messenger]];
-    
-    UIViewController *viewController = (UIViewController *)registrar.messenger;
+
+    UIViewController *viewController = [UIApplication sharedApplication].delegate.window.rootViewController;
     FlutterWebviewPlugin* instance = [[FlutterWebviewPlugin alloc] initWithViewController:viewController];
-    
+
     [registrar addMethodCallDelegate:instance channel:channel];
 }
 
@@ -48,7 +48,7 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
         result(nil);
     } else if ([@"reloadUrl" isEqualToString:call.method]) {
         [self reloadUrl:call];
-        result(nil);	
+        result(nil);
     } else if ([@"show" isEqualToString:call.method]) {
         [self show];
         result(nil);
@@ -58,6 +58,17 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     } else if ([@"stopLoading" isEqualToString:call.method]) {
         [self stopLoading];
         result(nil);
+    } else if ([@"cleanCookies" isEqualToString:call.method]) {
+        [self cleanCookies];
+    } else if ([@"back" isEqualToString:call.method]) {
+        [self back];
+        result(nil);
+    } else if ([@"forward" isEqualToString:call.method]) {
+        [self forward];
+        result(nil);
+    } else if ([@"reload" isEqualToString:call.method]) {
+        [self reload];
+        result(nil);
     } else {
         result(FlutterMethodNotImplemented);
     }
@@ -72,27 +83,27 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     NSString *userAgent = call.arguments[@"userAgent"];
     NSNumber *withZoom = call.arguments[@"withZoom"];
     NSNumber *scrollBar = call.arguments[@"scrollBar"];
-    
+
     if (clearCache != (id)[NSNull null] && [clearCache boolValue]) {
         [[NSURLCache sharedURLCache] removeAllCachedResponses];
     }
-    
+
     if (clearCookies != (id)[NSNull null] && [clearCookies boolValue]) {
         [[NSURLSession sharedSession] resetWithCompletionHandler:^{
         }];
     }
-    
+
     if (userAgent != (id)[NSNull null]) {
         [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}];
     }
-    
+
     CGRect rc;
-    if (rect != nil) {
+    if (rect != (id)[NSNull null]) {
         rc = [self parseRect:rect];
     } else {
         rc = self.viewController.view.bounds;
     }
-    
+
     self.webview = [[WKWebView alloc] initWithFrame:rc];
     self.webview.navigationDelegate = self;
     self.webview.scrollView.delegate = self;
@@ -100,8 +111,6 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     self.webview.scrollView.showsHorizontalScrollIndicator = [scrollBar boolValue];
     self.webview.scrollView.showsVerticalScrollIndicator = [scrollBar boolValue];
 
-
-
     _enableZoom = [withZoom boolValue];
 
     [self.viewController.view addSubview:self.webview];
@@ -138,11 +147,11 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
             } else {
                 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
                 NSDictionary *headers = call.arguments[@"headers"];
-                
+
                 if (headers != nil) {
                     [request setAllHTTPHeaderFields:headers];
                 }
-                
+
                 [self.webview loadRequest:request];
             }
         }
@@ -204,6 +213,26 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
         [self.webview stopLoading];
     }
 }
+- (void)back {
+    if (self.webview != nil) {
+        [self.webview goBack];
+    }
+}
+- (void)forward {
+    if (self.webview != nil) {
+        [self.webview goForward];
+    }
+}
+- (void)reload {
+    if (self.webview != nil) {
+        [self.webview reload];
+    }
+}
+
+- (void)cleanCookies {
+    [[NSURLSession sharedSession] resetWithCompletionHandler:^{
+        }];
+}
 
 #pragma mark -- WkWebView Delegate
 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
@@ -241,10 +270,7 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
 }
 
 - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
-    id data = [FlutterError errorWithCode:[NSString stringWithFormat:@"%ld", error.code]
-                                  message:error.localizedDescription
-                                  details:error.localizedFailureReason];
-    [channel invokeMethod:@"onError" arguments:data];
+    [channel invokeMethod:@"onError" arguments:@{@"code": [NSString stringWithFormat:@"%ld", error.code], @"error": error.localizedDescription}];
 }
 
 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {

+ 65 - 69
lib/src/base.dart

@@ -11,25 +11,24 @@ 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.
+/// Singleton class that communicate with a Webview Instance
 class FlutterWebviewPlugin {
-  final _channel = const MethodChannel(_kChannel);
+  factory FlutterWebviewPlugin() => _instance ??= FlutterWebviewPlugin._();
 
-  final _onDestroy = new StreamController<Null>.broadcast();
-  final _onUrlChanged = new StreamController<String>.broadcast();
-  final _onStateChanged = new StreamController<WebViewStateChanged>.broadcast();
-  final _onScrollXChanged = new StreamController<double>.broadcast();
-  final _onScrollYChanged = new StreamController<double>.broadcast();
-  final _onHttpError = new StreamController<WebViewHttpError>.broadcast();
+  FlutterWebviewPlugin._() {
+    _channel.setMethodCallHandler(_handleMessages);
+  }
 
   static FlutterWebviewPlugin _instance;
 
-  factory FlutterWebviewPlugin() => _instance ??= new FlutterWebviewPlugin._();
+  final _channel = const MethodChannel(_kChannel);
 
-  FlutterWebviewPlugin._() {
-    _channel.setMethodCallHandler(_handleMessages);
-  }
+  final _onDestroy = StreamController<Null>.broadcast();
+  final _onUrlChanged = StreamController<String>.broadcast();
+  final _onStateChanged = StreamController<WebViewStateChanged>.broadcast();
+  final _onScrollXChanged = StreamController<double>.broadcast();
+  final _onScrollYChanged = StreamController<double>.broadcast();
+  final _onHttpError = StreamController<WebViewHttpError>.broadcast();
 
   Future<Null> _handleMessages(MethodCall call) async {
     switch (call.method) {
@@ -39,21 +38,21 @@ class FlutterWebviewPlugin {
       case 'onUrlChanged':
         _onUrlChanged.add(call.arguments['url']);
         break;
-      case "onScrollXChanged":
-        _onScrollXChanged.add(call.arguments["xDirection"]);
+      case 'onScrollXChanged':
+        _onScrollXChanged.add(call.arguments['xDirection']);
         break;
-      case "onScrollYChanged":
-        _onScrollYChanged.add(call.arguments["yDirection"]);
+      case 'onScrollYChanged':
+        _onScrollYChanged.add(call.arguments['yDirection']);
         break;
-      case "onState":
+      case 'onState':
         _onStateChanged.add(
-          new WebViewStateChanged.fromMap(
-              new Map<String, dynamic>.from(call.arguments)),
+          WebViewStateChanged.fromMap(
+            Map<String, dynamic>.from(call.arguments),
+          ),
         );
         break;
       case 'onHttpError':
-        _onHttpError.add(
-            WebViewHttpError(call.arguments['code'], call.arguments['url']));
+        _onHttpError.add(WebViewHttpError(call.arguments['code'], call.arguments['url']));
         break;
     }
   }
@@ -95,19 +94,23 @@ class FlutterWebviewPlugin {
   /// - [withLocalUrl]: allow url as a local path
   ///     Allow local files on iOs > 9.0
   /// - [scrollBar]: enable or disable scrollbar
-  Future<Null> launch(String url,
-      {Map<String, String> headers,
-      bool withJavascript,
-      bool clearCache,
-      bool clearCookies,
-      bool hidden,
-      bool enableAppScheme,
-      Rect rect,
-      String userAgent,
-      bool withZoom,
-      bool withLocalStorage,
-      bool withLocalUrl,
-      bool scrollBar}) async {
+  Future<Null> launch(String url, {
+    Map<String, String> headers,
+    bool withJavascript,
+    bool clearCache,
+    bool clearCookies,
+    bool hidden,
+    bool enableAppScheme,
+    Rect rect,
+    String userAgent,
+    bool withZoom,
+    bool withLocalStorage,
+    bool withLocalUrl,
+    bool scrollBar,
+    bool supportMultipleWindows,
+    bool appCacheEnabled,
+    bool allowFileURLs,
+  }) async {
     final args = <String, dynamic>{
       'url': url,
       'withJavascript': withJavascript ?? true,
@@ -119,7 +122,10 @@ class FlutterWebviewPlugin {
       'withZoom': withZoom ?? false,
       'withLocalStorage': withLocalStorage ?? true,
       'withLocalUrl': withLocalUrl ?? false,
-      'scrollBar': scrollBar ?? true
+      'scrollBar': scrollBar ?? true,
+      'supportMultipleWindows': supportMultipleWindows ?? false,
+      'appCacheEnabled': appCacheEnabled ?? false,
+      'allowFileURLs': allowFileURLs ?? false,
     };
 
     if (headers != null) {
@@ -131,7 +137,7 @@ class FlutterWebviewPlugin {
         'left': rect.left,
         'top': rect.top,
         'width': rect.width,
-        'height': rect.height
+        'height': rect.height,
       };
     }
     await _channel.invokeMethod('launch', args);
@@ -145,44 +151,34 @@ class FlutterWebviewPlugin {
 
   /// Close the Webview
   /// Will trigger the [onDestroy] event
-  Future close() => _channel.invokeMethod('close');
+  Future<Null> close() async => await _channel.invokeMethod('close');
 
   /// Reloads the WebView.
-  /// This is only available on Android for now.
-  Future reload() => _channel.invokeMethod('reload');
+  Future<Null> reload() async => await _channel.invokeMethod('reload');
 
   /// Navigates back on the Webview.
-  /// This is only available on Android for now.
-  Future goBack() => _channel.invokeMethod('back');
+  Future<Null> goBack() async => await _channel.invokeMethod('back');
 
   /// Navigates forward on the Webview.
-  /// This is only available on Android for now.
-  Future goForward() => _channel.invokeMethod('forward');
+  Future<Null> goForward() async => await _channel.invokeMethod('forward');
 
   // Hides the webview
-  Future hide() => _channel.invokeMethod('hide');
+  Future<Null> hide() async => await _channel.invokeMethod('hide');
 
   // Shows the webview
-  Future show() => _channel.invokeMethod('show');
+  Future<Null> show() async => await _channel.invokeMethod('show');
 
-  // Reload webview with a new url
-  Future reloadUrl(String url) async {
+  // Reload webview with a url
+  Future<Null> reloadUrl(String url) async {
     final args = <String, String>{'url': url};
     await _channel.invokeMethod('reloadUrl', args);
   }
 
-  // Stops current loading process
-  Future stopLoading() => _channel.invokeMethod("stopLoading");
-
-  /// adds the plugin as ActivityResultListener
-  /// Only needed and used on Android
-  Future registerAcitivityResultListener() =>
-      _channel.invokeMethod('registerAcitivityResultListener');
+  // Clean cookies on WebView
+  Future<Null> cleanCookies() async => await _channel.invokeMethod('cleanCookies');
 
-  /// removes the plugin as ActivityResultListener
-  /// Only needed and used on Android
-  Future removeAcitivityResultListener() =>
-      _channel.invokeMethod('removeAcitivityResultListener');
+  // Stops current loading process
+  Future<Null> stopLoading() async => await _channel.invokeMethod('stopLoading');
 
   /// Close all Streams
   void dispose() {
@@ -201,8 +197,8 @@ class FlutterWebviewPlugin {
 
     if (cookiesString?.isNotEmpty == true) {
       cookiesString.split(';').forEach((String cookie) {
-        final splited = cookie.split('=');
-        cookies[splited[0]] = splited[1];
+        final split = cookie.split('=');
+        cookies[split[0]] = split[1];
       });
     }
 
@@ -216,17 +212,13 @@ class FlutterWebviewPlugin {
       'left': rect.left,
       'top': rect.top,
       'width': rect.width,
-      'height': rect.height
+      '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) {
@@ -242,13 +234,17 @@ class WebViewStateChanged {
         t = WebViewState.finishLoad;
         break;
     }
-    return new WebViewStateChanged(t, map['url'], map['navigationType']);
+    return WebViewStateChanged(t, map['url'], map['navigationType']);
   }
+
+  final WebViewState type;
+  final String url;
+  final int navigationType;
 }
 
 class WebViewHttpError {
+  WebViewHttpError(this.code, this.url);
+
   final String url;
   final String code;
-
-  WebViewHttpError(this.code, this.url);
 }

+ 135 - 74
lib/src/webview_scaffold.dart

@@ -2,12 +2,39 @@ import 'dart:async';
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
 
 import 'base.dart';
 
 class WebviewScaffold extends StatefulWidget {
+
+  const WebviewScaffold({
+    Key key,
+    this.appBar,
+    @required this.url,
+    this.headers,
+    this.withJavascript,
+    this.clearCache,
+    this.clearCookies,
+    this.enableAppScheme,
+    this.userAgent,
+    this.primary = true,
+    this.persistentFooterButtons,
+    this.bottomNavigationBar,
+    this.withZoom,
+    this.withLocalStorage,
+    this.withLocalUrl,
+    this.scrollBar,
+    this.supportMultipleWindows,
+    this.appCacheEnabled,
+    this.hidden = false,
+    this.initialChild,
+    this.allowFileURLs,
+  }) : super(key: key);
+
   final PreferredSizeWidget appBar;
   final String url;
+  final Map<String, String> headers;
   final bool withJavascript;
   final bool clearCache;
   final bool clearCookies;
@@ -20,112 +47,146 @@ class WebviewScaffold extends StatefulWidget {
   final bool withLocalStorage;
   final bool withLocalUrl;
   final bool scrollBar;
-
-  final Map<String, String> headers;
-
-  const WebviewScaffold(
-      {Key key,
-      this.appBar,
-      @required this.url,
-      this.headers,
-      this.withJavascript,
-      this.clearCache,
-      this.clearCookies,
-      this.enableAppScheme,
-      this.userAgent,
-      this.primary = true,
-      this.persistentFooterButtons,
-      this.bottomNavigationBar,
-      this.withZoom,
-      this.withLocalStorage,
-      this.withLocalUrl,
-      this.scrollBar})
-      : super(key: key);
+  final bool supportMultipleWindows;
+  final bool appCacheEnabled;
+  final bool hidden;
+  final Widget initialChild;
+  final bool allowFileURLs;
 
   @override
-  _WebviewScaffoldState createState() => new _WebviewScaffoldState();
+  _WebviewScaffoldState createState() => _WebviewScaffoldState();
 }
 
 class _WebviewScaffoldState extends State<WebviewScaffold> {
-  final webviewReference = new FlutterWebviewPlugin();
+  final webviewReference = FlutterWebviewPlugin();
   Rect _rect;
   Timer _resizeTimer;
+  StreamSubscription<WebViewStateChanged> _onStateChanged;
 
   @override
   void initState() {
     super.initState();
     webviewReference.close();
+
+    if (widget.hidden) {
+      _onStateChanged = webviewReference.onStateChanged.listen((WebViewStateChanged state) {
+        if (state.type == WebViewState.finishLoad) {
+          webviewReference.show();
+        }
+      });
+    }
   }
 
   @override
   void dispose() {
     super.dispose();
+    _resizeTimer?.cancel();
     webviewReference.close();
+    if (widget.hidden) {
+      _onStateChanged.cancel();
+    }
     webviewReference.dispose();
   }
 
   @override
   Widget build(BuildContext context) {
-    if (_rect == null) {
-      _rect = _buildRect(context);
-      webviewReference.launch(widget.url,
-          headers: widget.headers,
-          withJavascript: widget.withJavascript,
-          clearCache: widget.clearCache,
-          clearCookies: widget.clearCookies,
-          enableAppScheme: widget.enableAppScheme,
-          userAgent: widget.userAgent,
-          rect: _rect,
-          withZoom: widget.withZoom,
-          withLocalStorage: widget.withLocalStorage,
-          withLocalUrl: widget.withLocalUrl,
-          scrollBar: widget.scrollBar);
-    } else {
-      final 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,
-        persistentFooterButtons: widget.persistentFooterButtons,
-        bottomNavigationBar: widget.bottomNavigationBar,
-        body: const Center(child: const CircularProgressIndicator()));
+    return Scaffold(
+      appBar: widget.appBar,
+      persistentFooterButtons: widget.persistentFooterButtons,
+      bottomNavigationBar: widget.bottomNavigationBar,
+      body: _WebviewPlaceholder(
+        onRectChanged: (Rect value) {
+          if (_rect == null) {
+            _rect = value;
+            webviewReference.launch(
+              widget.url,
+              headers: widget.headers,
+              withJavascript: widget.withJavascript,
+              clearCache: widget.clearCache,
+              clearCookies: widget.clearCookies,
+              hidden: widget.hidden,
+              enableAppScheme: widget.enableAppScheme,
+              userAgent: widget.userAgent,
+              rect: _rect,
+              withZoom: widget.withZoom,
+              withLocalStorage: widget.withLocalStorage,
+              withLocalUrl: widget.withLocalUrl,
+              scrollBar: widget.scrollBar,
+              supportMultipleWindows: widget.supportMultipleWindows,
+              appCacheEnabled: widget.appCacheEnabled,
+              allowFileURLs: widget.allowFileURLs,
+            );
+          } else {
+            if (_rect != value) {
+              _rect = value;
+              _resizeTimer?.cancel();
+              _resizeTimer = Timer(const Duration(milliseconds: 250), () {
+                // avoid resizing to fast when build is called multiple time
+                webviewReference.resize(_rect);
+              });
+            }
+          }
+        },
+        child: widget.initialChild ?? const Center(child: const CircularProgressIndicator()),
+      ),
+    );
   }
+}
 
-  Rect _buildRect(BuildContext context) {
-    final fullscreen = widget.appBar == null;
+class _WebviewPlaceholder extends SingleChildRenderObjectWidget {
+  const _WebviewPlaceholder({
+    Key key,
+    @required this.onRectChanged,
+    Widget child,
+  }) : super(key: key, child: child);
 
-    final mediaQuery = MediaQuery.of(context);
-    final topPadding = widget.primary ? mediaQuery.padding.top : 0.0;
-    final top =
-        fullscreen ? 0.0 : widget.appBar.preferredSize.height + topPadding;
+  final ValueChanged<Rect> onRectChanged;
 
-    var height = mediaQuery.size.height - top;
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _WebviewPlaceholderRender(
+      onRectChanged: onRectChanged,
+    );
+  }
 
-    if (widget.bottomNavigationBar != null) {
-      height -= 56.0 +
-          mediaQuery.padding
-              .bottom; // todo(lejard_h) find a way to determine bottomNavigationBar programmatically
-    }
+  @override
+  void updateRenderObject(BuildContext context, _WebviewPlaceholderRender renderObject) {
+    renderObject..onRectChanged = onRectChanged;
+  }
+}
+
+class _WebviewPlaceholderRender extends RenderProxyBox {
+  _WebviewPlaceholderRender({
+    RenderBox child,
+    ValueChanged<Rect> onRectChanged,
+  })  : _callback = onRectChanged,
+        super(child);
+
+  ValueChanged<Rect> _callback;
+  Rect _rect;
+
+  Rect get rect => _rect;
 
-    if (widget.persistentFooterButtons != null) {
-      height -=
-          53.0; // todo(lejard_h) find a way to determine persistentFooterButtons programmatically
-      if (widget.bottomNavigationBar == null) {
-        height -= mediaQuery.padding.bottom;
-      }
+  set onRectChanged(ValueChanged<Rect> callback) {
+    if (callback != _callback) {
+      _callback = callback;
+      notifyRect();
     }
+  }
 
-    if (height < 0.0) {
-      height = 0.0;
+  void notifyRect() {
+    if (_callback != null && _rect != null) {
+      _callback(_rect);
     }
+  }
 
-    return new Rect.fromLTWH(0.0, top, mediaQuery.size.width, height);
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    super.paint(context, offset);
+    final rect = offset & size;
+    if (_rect != rect) {
+      _rect = rect;
+      notifyRect();
+    }
   }
 }

+ 5 - 2
pubspec.yaml

@@ -4,11 +4,14 @@ authors:
 - Hadrien Lejard <hadrien.lejard@gmail.com>
 - Toufik Zitouni <toufiksapps@gmail.com>
 - Pedia <kpedia@163.com>
+- Simon Lightfoot <simon@devangels.london>
+- Rafal Wachol <gorudonu@gmail.com>
 homepage: https://github.com/dart-flitter/flutter_webview_plugin
-version: 0.2.1+2
+version: 0.3.0+2
+maintainer: Simon Lightfoot (@slightfoot)
 
 environment:
-  sdk: ">=2.0.0-dev <3.0.0"
+  sdk: ">=2.0.0 <3.0.0"
 
 flutter:
   plugin:

Vissa filer visades inte eftersom för många filer har ändrats