Browse Source

Merge branch 'master' of github.com:dart-flitter/flutter_webview_plugin

# Conflicts:
#	android/src/main/java/com/flutter_webview_plugin/WebviewManager.java
#	example/lib/main.dart
#	lib/src/base.dart
theblackcat102 7 years ago
parent
commit
4f119d6324

+ 11 - 0
CHANGELOG.md

@@ -1,3 +1,14 @@
+# 0.2.0
+
+- update sdk
+- prevent negative webview height in scaffold
+- handle type error in getCookies
+- Support file upload via WebView on Android
+- fix WebviewScaffold crash on iOS
+- Scrollbar functionality to Web view
+- Add support of HTTP errors
+- Add headers when loading url
+
 # 0.1.6
 
 - fix onStateChanged

+ 4 - 2
README.md

@@ -84,7 +84,8 @@ flutterWebviewPlugin.launch(url,
 
 ```dart
 Future<Null> launch(String url,
-         {bool withJavascript: true,
+         {Map<String, String> headers: null,
+         bool withJavascript: true,
          bool clearCache: false,
          bool clearCookies: false,
          bool hidden: false,
@@ -92,7 +93,8 @@ Future<Null> launch(String url,
          Rect rect: null,
          String userAgent: null,
          bool withZoom: false,
-         bool withLocalStorage: true});
+         bool withLocalStorage: true,
+         bool scrollBar: true});
 ```
 ```dart
 Future<String> evalJavascript(String code);

+ 120 - 1
analysis_options.yaml

@@ -1,2 +1,121 @@
 analyzer:
-  strong-mode: true
+
+linter:
+  rules:
+  # these rules are documented on and in the same order as
+  # the Dart Lint rules page to make maintenance easier
+  # https://github.com/dart-lang/linter/blob/master/example/all.yaml
+  - always_declare_return_types
+  - always_put_control_body_on_new_line
+  # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
+  - always_require_non_null_named_parameters
+  # - always_specify_types
+  - annotate_overrides
+  # - avoid_annotating_with_dynamic # conflicts with always_specify_types
+  - avoid_as
+  # - avoid_bool_literals_in_conditional_expressions # not yet tested
+  # - avoid_catches_without_on_clauses # we do this commonly
+  # - avoid_catching_errors # we do this commonly
+  - avoid_classes_with_only_static_members
+  # - avoid_double_and_int_checks # only useful when targeting JS runtime
+  - avoid_empty_else
+  - avoid_field_initializers_in_const_classes
+  # - avoid_function_literals_in_foreach_calls
+  - avoid_init_to_null
+  # - avoid_js_rounded_ints # only useful when targeting JS runtime
+  - avoid_null_checks_in_equality_operators
+  # - avoid_positional_boolean_parameters # not yet tested
+  # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
+  - avoid_relative_lib_imports
+  - avoid_renaming_method_parameters
+  - avoid_return_types_on_setters
+  # - avoid_returning_null # we do this commonly
+  # - avoid_returning_this # https://github.com/dart-lang/linter/issues/842
+  # - avoid_setters_without_getters # not yet tested
+  # - avoid_single_cascade_in_expression_statements # not yet tested
+  - avoid_slow_async_io
+  # - avoid_types_as_parameter_names # https://github.com/dart-lang/linter/pull/954/files
+  # - avoid_types_on_closure_parameters # conflicts with always_specify_types
+  # - avoid_unused_constructor_parameters # https://github.com/dart-lang/linter/pull/847
+  - await_only_futures
+  - camel_case_types
+  - cancel_subscriptions
+  # - cascade_invocations # not yet tested
+  # - close_sinks # https://github.com/flutter/flutter/issues/5789
+  # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153
+  # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
+  - control_flow_in_finally
+  - directives_ordering
+  - empty_catches
+  - empty_constructor_bodies
+  - empty_statements
+  - hash_and_equals
+  - implementation_imports
+  # - invariant_booleans # https://github.com/flutter/flutter/issues/5790
+  - iterable_contains_unrelated_type
+  # - join_return_with_assignment # not yet tested
+  - library_names
+  - library_prefixes
+  - list_remove_unrelated_type
+  # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791
+  - no_adjacent_strings_in_list
+  - no_duplicate_case_values
+  - non_constant_identifier_names
+  # - omit_local_variable_types # opposite of always_specify_types
+  # - one_member_abstracts # too many false positives
+  # - only_throw_errors # https://github.com/flutter/flutter/issues/5792
+  - overridden_fields
+  - package_api_docs
+  - package_names
+  - package_prefixed_library_names
+  # - parameter_assignments # we do this commonly
+  - prefer_adjacent_string_concatenation
+  - prefer_asserts_in_initializer_lists
+  - prefer_bool_in_asserts
+  - prefer_collection_literals
+  - prefer_conditional_assignment
+  - prefer_const_constructors
+  - prefer_const_constructors_in_immutables
+  - prefer_const_declarations
+  - prefer_const_literals_to_create_immutables
+  # - prefer_constructors_over_static_methods # not yet tested
+  - prefer_contains
+  - prefer_equal_for_default_values
+  # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods
+  - prefer_final_fields
+  - prefer_final_locals
+  # - prefer_foreach
+  # - prefer_function_declarations_over_variables # not yet tested
+  - prefer_initializing_formals
+  # - prefer_interpolation_to_compose_strings # not yet tested
+  # - prefer_iterable_whereType # https://github.com/dart-lang/sdk/issues/32463
+  - prefer_is_empty
+  - prefer_is_not_empty
+  - prefer_single_quotes
+  - prefer_typing_uninitialized_variables
+  - recursive_getters
+  - slash_for_doc_comments
+  - sort_constructors_first
+  - sort_unnamed_constructors_first
+  - super_goes_last
+  - test_types_in_equals
+  - throw_in_finally
+  # - type_annotate_public_apis # subset of always_specify_types
+  - type_init_formals
+  # - unawaited_futures # https://github.com/flutter/flutter/issues/5793
+  - unnecessary_brace_in_string_interps
+  - unnecessary_getters_setters
+  # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498
+  - unnecessary_null_aware_assignments
+  - unnecessary_null_in_if_null_operators
+  - unnecessary_overrides
+  - unnecessary_parenthesis
+  # - unnecessary_statements # not yet tested
+  - unnecessary_this
+  - unrelated_type_equality_checks
+  - use_rethrow_when_possible
+  # - use_setters_to_change_properties # not yet tested
+  # - use_string_buffers # https://github.com/dart-lang/linter/pull/664
+  # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review
+  - valid_regexps
+  # - void_checks # not yet tested

+ 1 - 0
android/build.gradle

@@ -26,6 +26,7 @@ android {
 
     defaultConfig {
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        minSdkVersion 16
     }
     lintOptions {
         disable 'InvalidPackage'

+ 11 - 0
android/src/main/java/com/flutter_webview_plugin/BrowserClient.java

@@ -1,6 +1,8 @@
 package com.flutter_webview_plugin;
 
 import android.graphics.Bitmap;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 
@@ -37,4 +39,13 @@ public class BrowserClient extends WebViewClient {
         FlutterWebviewPlugin.channel.invokeMethod("onState", data);
 
     }
+
+    @Override
+    public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
+        super.onReceivedHttpError(view, request, errorResponse);
+        Map<String, Object> data = new HashMap<>();
+        data.put("url", request.getUrl().toString());
+        data.put("code", Integer.toString(errorResponse.getStatusCode()));
+        FlutterWebviewPlugin.channel.invokeMethod("onHttpError", data);
+    }
 }

+ 19 - 3
android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java

@@ -3,6 +3,7 @@ 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;
@@ -17,7 +18,7 @@ import io.flutter.plugin.common.PluginRegistry;
 /**
  * FlutterWebviewPlugin
  */
-public class FlutterWebviewPlugin implements MethodCallHandler {
+public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.ActivityResultListener {
     private Activity activity;
     private WebviewManager webViewManager;
     static MethodChannel channel;
@@ -25,7 +26,8 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
 
     public static void registerWith(PluginRegistry.Registrar registrar) {
         channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
-        FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity());
+        final FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity());
+        registrar.addActivityResultListener(instance);
         channel.setMethodCallHandler(instance);
     }
 
@@ -81,6 +83,8 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
         boolean clearCookies = call.argument("clearCookies");
         boolean withZoom = call.argument("withZoom");
         boolean withLocalStorage = call.argument("withLocalStorage");
+        Map<String, String> headers = call.argument("headers");
+        boolean scrollBar = call.argument("scrollBar");
 
         if (webViewManager == null || webViewManager.closed == true) {
             webViewManager = new WebviewManager(activity);
@@ -96,8 +100,10 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
                 clearCookies,
                 userAgent,
                 url,
+                headers,
                 withZoom,
-                withLocalStorage
+                withLocalStorage,
+                scrollBar
         );
         result.success(null);
     }
@@ -163,6 +169,8 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
                     false,
                     "",
                     url,
+                    null,
+                    false,
                     false,
                     false
             );
@@ -196,4 +204,12 @@ public class FlutterWebviewPlugin implements MethodCallHandler {
         final float scale = context.getResources().getDisplayMetrics().density;
         return (int) (dp * scale + 0.5f);
     }
+
+    @Override
+    public boolean onActivityResult(int i, int i1, Intent intent) {
+        if(webViewManager != null && webViewManager.resultHandler != null){
+            return webViewManager.resultHandler.handleResult(i, i1, intent);
+        }
+        return false;
+    }
 }

+ 118 - 6
android/src/main/java/com/flutter_webview_plugin/WebviewManager.java

@@ -1,5 +1,7 @@
 package com.flutter_webview_plugin;
 
+import android.content.Intent;
+import android.net.Uri;
 import android.util.Log;
 import android.annotation.TargetApi;
 import android.app.Activity;
@@ -9,16 +11,17 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.webkit.CookieManager;
 import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.FrameLayout;
 
-import java.util.HashMap;
 import java.util.Map;
 
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
 
+import static android.app.Activity.RESULT_OK;
 
 /**
  * Created by lejard_h on 20/12/2017.
@@ -26,12 +29,54 @@ import io.flutter.plugin.common.MethodChannel;
 
 class WebviewManager {
 
+    private ValueCallback<Uri> mUploadMessage;
+    private ValueCallback<Uri[]> mUploadMessageArray;
+    private final static int FILECHOOSER_RESULTCODE=1;
+
+    @TargetApi(7)
+    class ResultHandler {
+        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)};
+                            }
+                        }
+                        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();
+                        mUploadMessage.onReceiveValue(result);
+                        mUploadMessage = null;
+                    }
+                    handled = true;
+                }
+            }
+            return handled;
+        }
+    }
+
     boolean closed = false;
     WebView webView;
-    ObservableWebView observableWebView;
+    Activity activity;
+    ResultHandler resultHandler;
 
-    WebviewManager(Activity activity) {
-        this.webView = (WebView) new ObservableWebView(activity);
+    WebviewManager(final Activity activity) {
+        this.webView = new WebView(activity);
+        this.activity = activity;
+        this.resultHandler = new ResultHandler();
         WebViewClient webViewClient = new BrowserClient();
         webView.setOnKeyListener(new View.OnKeyListener() {
             @Override
@@ -65,6 +110,65 @@ class WebviewManager {
         });
 
         webView.setWebViewClient(webViewClient);
+        webView.setWebChromeClient(new WebChromeClient()
+        {
+            //The undocumented magic method override
+            //Eclipse will swear at you if you try to put @Override here
+            // For Android 3.0+
+            public void openFileChooser(ValueCallback<Uri> uploadMsg) {
+
+                mUploadMessage = uploadMsg;
+                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
+                i.addCategory(Intent.CATEGORY_OPENABLE);
+                i.setType("image/*");
+                activity.startActivityForResult(Intent.createChooser(i,"File Chooser"), FILECHOOSER_RESULTCODE);
+
+            }
+
+            // For Android 3.0+
+            public void openFileChooser( ValueCallback uploadMsg, String acceptType ) {
+                mUploadMessage = uploadMsg;
+                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
+                i.addCategory(Intent.CATEGORY_OPENABLE);
+                i.setType("*/*");
+               activity.startActivityForResult(
+                        Intent.createChooser(i, "File Browser"),
+                        FILECHOOSER_RESULTCODE);
+            }
+
+            //For Android 4.1
+            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){
+                mUploadMessage = uploadMsg;
+                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
+                i.addCategory(Intent.CATEGORY_OPENABLE);
+                i.setType("image/*");
+                activity.startActivityForResult( Intent.createChooser( i, "File Chooser" ), FILECHOOSER_RESULTCODE );
+
+            }
+
+            //For Android 5.0+
+            public boolean onShowFileChooser(
+                    WebView webView, ValueCallback<Uri[]> filePathCallback,
+                    FileChooserParams fileChooserParams){
+                if(mUploadMessageArray != null){
+                    mUploadMessageArray.onReceiveValue(null);
+                }
+                mUploadMessageArray = filePathCallback;
+
+                Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
+                contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
+                contentSelectionIntent.setType("*/*");
+                Intent[] intentArray;
+                intentArray = new Intent[0];
+
+                Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
+                chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
+                chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
+                chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
+                activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
+                return true;
+            }
+        });
     }
 
     private void clearCookies() {
@@ -85,7 +189,7 @@ class WebviewManager {
         webView.clearFormData();
     }
 
-    void openUrl(boolean withJavascript, boolean clearCache, boolean hidden, boolean clearCookies, String userAgent, String url, boolean withZoom, boolean withLocalStorage) {
+    void openUrl(boolean withJavascript, boolean clearCache, boolean hidden, boolean clearCookies, String userAgent, String url, Map<String, String> headers, boolean withZoom, boolean withLocalStorage, boolean scrollBar) {
         webView.getSettings().setJavaScriptEnabled(withJavascript);
         webView.getSettings().setBuiltInZoomControls(withZoom);
         webView.getSettings().setSupportZoom(withZoom);
@@ -106,8 +210,16 @@ class WebviewManager {
         if (userAgent != null) {
             webView.getSettings().setUserAgentString(userAgent);
         }
+      
+        if(!scrollBar){
+            webView.setVerticalScrollBarEnabled(false);
+        }
 
-        webView.loadUrl(url);
+        if (headers != null) {
+            webView.loadUrl(url, headers);
+        } else {
+            webView.loadUrl(url);
+        }
     }
 
     void close(MethodCall call, MethodChannel.Result result) {

+ 39 - 31
example/lib/main.dart

@@ -5,9 +5,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";
+    '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://flutter.io";
+String selectedUrl = 'https://flutter.io';
 
 void main() {
   runApp(new MyApp());
@@ -22,11 +22,11 @@ class MyApp extends StatelessWidget {
         primarySwatch: Colors.blue,
       ),
       routes: {
-        "/": (_) => new MyHomePage(title: "Flutter WebView Demo"),
-        "/widget": (_) => new WebviewScaffold(
+        '/': (_) => const MyHomePage(title: 'Flutter WebView Demo'),
+        '/widget': (_) => new WebviewScaffold(
               url: selectedUrl,
               appBar: new AppBar(
-                title: new Text("Widget webview"),
+                title: const Text('Widget webview'),
               ),
               withZoom: true,
               withLocalStorage: true,
@@ -37,7 +37,7 @@ class MyApp extends StatelessWidget {
 }
 
 class MyHomePage extends StatefulWidget {
-  MyHomePage({Key key, this.title}) : super(key: key);
+  const MyHomePage({Key key, this.title}) : super(key: key);
 
   final String title;
 
@@ -58,19 +58,19 @@ class _MyHomePageState extends State<MyHomePage> {
   // On urlChanged stream
   StreamSubscription<WebViewStateChanged> _onStateChanged;
 
-  StreamSubscription<double> _onScrollYChanged,_onScrollXChanged;
+  StreamSubscription<WebViewHttpError> _onHttpError;
 
-  TextEditingController _urlCtrl = new TextEditingController(text: selectedUrl);
+  final _urlCtrl = new TextEditingController(text: selectedUrl);
 
-  TextEditingController _codeCtrl =
-      new TextEditingController(text: "window.navigator.userAgent");
+  final _codeCtrl =
+      new TextEditingController(text: 'window.navigator.userAgent');
 
-  GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
+  final _scaffoldKey = new GlobalKey<ScaffoldState>();
 
   final _history = [];
 
   @override
-  initState() {
+  void initState() {
     super.initState();
 
     flutterWebviewPlugin.close();
@@ -83,8 +83,8 @@ class _MyHomePageState extends State<MyHomePage> {
     _onDestroy = flutterWebviewPlugin.onDestroy.listen((_) {
       if (mounted) {
         // Actions like show a info toast.
-        _scaffoldKey.currentState
-            .showSnackBar(new SnackBar(content: new Text("Webview Destroyed")));
+        _scaffoldKey.currentState.showSnackBar(
+            const SnackBar(content: const Text('Webview Destroyed')));
       }
     });
 
@@ -92,7 +92,7 @@ class _MyHomePageState extends State<MyHomePage> {
     _onUrlChanged = flutterWebviewPlugin.onUrlChanged.listen((String url) {
       if (mounted) {
         setState(() {
-          _history.add("onUrlChanged: $url");
+          _history.add('onUrlChanged: $url');
         });
       }
     });
@@ -118,7 +118,16 @@ class _MyHomePageState extends State<MyHomePage> {
         flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged state) {
       if (mounted) {
         setState(() {
-          _history.add("onStateChanged: ${state.type} ${state.url}");
+          _history.add('onStateChanged: ${state.type} ${state.url}');
+        });
+      }
+    });
+
+    _onHttpError =
+        flutterWebviewPlugin.onHttpError.listen((WebViewHttpError error) {
+      if (mounted) {
+        setState(() {
+          _history.add('onHttpError: ${error.code} ${error.url}');
         });
       }
     });
@@ -130,8 +139,7 @@ class _MyHomePageState extends State<MyHomePage> {
     _onDestroy.cancel();
     _onUrlChanged.cancel();
     _onStateChanged.cancel();
-    _onScrollYChanged.cancel();
-    _onScrollXChanged.cancel();
+    _onHttpError.cancel();
 
     flutterWebviewPlugin.dispose();
 
@@ -143,7 +151,7 @@ class _MyHomePageState extends State<MyHomePage> {
     return new Scaffold(
       key: _scaffoldKey,
       appBar: new AppBar(
-        title: new Text('Plugin example app'),
+        title: const Text('Plugin example app'),
       ),
       body: new Column(
         mainAxisAlignment: MainAxisAlignment.center,
@@ -159,25 +167,25 @@ class _MyHomePageState extends State<MyHomePage> {
                       0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
                   userAgent: kAndroidUserAgent);
             },
-            child: new Text("Open Webview (rect)"),
+            child: const Text('Open Webview (rect)'),
           ),
           new RaisedButton(
             onPressed: () {
               flutterWebviewPlugin.launch(selectedUrl, hidden: true);
             },
-            child: new Text("Open 'hidden' Webview"),
+            child: const Text('Open "hidden" Webview'),
           ),
           new RaisedButton(
             onPressed: () {
               flutterWebviewPlugin.launch(selectedUrl);
             },
-            child: new Text("Open Fullscreen Webview"),
+            child: const Text('Open Fullscreen Webview'),
           ),
           new RaisedButton(
             onPressed: () {
-              Navigator.of(context).pushNamed("/widget");
+              Navigator.of(context).pushNamed('/widget');
             },
-            child: new Text("Open widget webview"),
+            child: const Text('Open widget webview'),
           ),
           new Container(
             padding: const EdgeInsets.all(24.0),
@@ -185,15 +193,15 @@ class _MyHomePageState extends State<MyHomePage> {
           ),
           new RaisedButton(
             onPressed: () {
-              Future<String> future =
+              final future =
                   flutterWebviewPlugin.evalJavascript(_codeCtrl.text);
               future.then((String result) {
                 setState(() {
-                  _history.add("eval: $result");
+                  _history.add('eval: $result');
                 });
               });
             },
-            child: new Text("Eval some javascript"),
+            child: const Text('Eval some javascript'),
           ),
           new RaisedButton(
             onPressed: () {
@@ -202,19 +210,19 @@ class _MyHomePageState extends State<MyHomePage> {
               });
               flutterWebviewPlugin.close();
             },
-            child: new Text("Close"),
+            child: const Text('Close'),
           ),
           new RaisedButton(
             onPressed: () {
               flutterWebviewPlugin.getCookies().then((m) {
                 setState(() {
-                  _history.add("cookies: $m");
+                  _history.add('cookies: $m');
                 });
               });
             },
-            child: new Text("Cookies"),
+            child: const Text('Cookies'),
           ),
-          new Text(_history.sublist(_history.length-10, _history.length ).join("\n")),
+          new Text(_history.join('\n'))
         ],
       ),
     );

+ 19 - 1
ios/Classes/FlutterWebviewPlugin.m

@@ -68,6 +68,7 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     _enableAppScheme = call.arguments[@"enableAppScheme"];
     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];
@@ -93,6 +94,8 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     self.webview.navigationDelegate = self;
     self.webview.scrollView.delegate = self;
     self.webview.hidden = [hidden boolValue];
+    self.webview.scrollView.showsHorizontalScrollIndicator = [scrollBar boolValue];
+    self.webview.scrollView.showsVerticalScrollIndicator = [scrollBar boolValue];
 
 
 
@@ -130,7 +133,13 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
                     @throw @"not available on version earlier than ios 9.0";
                 }
             } else {
-                NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
+                NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
+                NSDictionary *headers = call.arguments[@"headers"];
+                
+                if (headers != nil) {
+                    [request setAllHTTPHeaderFields:headers];
+                }
+                
                 [self.webview loadRequest:request];
             }
         }
@@ -230,6 +239,15 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     [channel invokeMethod:@"onError" arguments:data];
 }
 
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
+    if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
+        NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response;
+
+        [channel invokeMethod:@"onHttpError" arguments:@{@"code": [NSString stringWithFormat:@"%ld", response.statusCode], @"url": webView.URL.absoluteString}];
+    }
+    decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
 #pragma mark -- UIScrollViewDelegate
 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
     if (scrollView.pinchGestureRecognizer.isEnabled != _enableZoom) {

+ 81 - 53
lib/src/base.dart

@@ -19,9 +19,9 @@ class FlutterWebviewPlugin {
   final _onDestroy = new StreamController<Null>.broadcast();
   final _onUrlChanged = new StreamController<String>.broadcast();
   final _onStateChanged = new StreamController<WebViewStateChanged>.broadcast();
-  final _onError = new StreamController<String>.broadcast();
   final _onScrollXChanged = new StreamController<double>.broadcast();
   final _onScrollYChanged = new StreamController<double>.broadcast();
+  final _onHttpError = new StreamController<WebViewHttpError>.broadcast();
 
   static FlutterWebviewPlugin _instance;
 
@@ -33,11 +33,11 @@ class FlutterWebviewPlugin {
 
   Future<Null> _handleMessages(MethodCall call) async {
     switch (call.method) {
-      case "onDestroy":
+      case 'onDestroy':
         _onDestroy.add(null);
         break;
-      case "onUrlChanged":
-        _onUrlChanged.add(call.arguments["url"]);
+      case 'onUrlChanged':
+        _onUrlChanged.add(call.arguments['url']);
         break;
       case "onScrollXChanged":
         _onScrollXChanged.add(call.arguments["xDirection"]);
@@ -51,8 +51,9 @@ class FlutterWebviewPlugin {
               new Map<String, dynamic>.from(call.arguments)),
         );
         break;
-      case "onError":
-        _onError.add(call.arguments);
+      case 'onHttpError':
+        _onHttpError.add(
+            WebViewHttpError(call.arguments['code'], call.arguments['url']));
         break;
     }
   }
@@ -75,7 +76,10 @@ class FlutterWebviewPlugin {
   /// Listening web view x position scroll change
   Stream<double> get onScrollXChanged => _onScrollXChanged.stream;
 
+  Stream<WebViewHttpError> get onHttpError => _onHttpError.stream;
+
   /// Start the Webview with [url]
+  /// - [headers] specify additional HTTP headers
   /// - [withJavascript] enable Javascript or not for the Webview
   ///     iOS WebView: Not implemented yet
   /// - [clearCache] clear the cache of the Webview
@@ -91,8 +95,10 @@ class FlutterWebviewPlugin {
   ///     It is always enabled in UIWebView of iOS and  can not be disabled.
   /// - [withLocalUrl]: allow url as a local path
   ///     Allow local files on iOs > 9.0
+  /// - [scrollBar]: enable or disable scrollbar
   Future<Null> launch(String url,
-      {bool withJavascript,
+      {Map<String, String> headers,
+      bool withJavascript,
       bool clearCache,
       bool clearCookies,
       bool hidden,
@@ -101,25 +107,32 @@ class FlutterWebviewPlugin {
       String userAgent,
       bool withZoom,
       bool withLocalStorage,
-      bool withLocalUrl}) async {
-    Map<String, dynamic> args = {
-      "url": url,
-      "withJavascript": withJavascript ?? true,
-      "clearCache": clearCache ?? false,
-      "hidden": hidden ?? false,
-      "clearCookies": clearCookies ?? false,
-      "enableAppScheme": enableAppScheme ?? true,
-      "userAgent": userAgent,
-      "withZoom": withZoom ?? false,
-      "withLocalStorage": withLocalStorage ?? true,
-      "withLocalUrl": withLocalUrl ?? false
+      bool withLocalUrl,
+      bool scrollBar}) async {
+    final args = <String, dynamic>{
+      'url': url,
+      'withJavascript': withJavascript ?? true,
+      'clearCache': clearCache ?? false,
+      'hidden': hidden ?? false,
+      'clearCookies': clearCookies ?? false,
+      'enableAppScheme': enableAppScheme ?? true,
+      'userAgent': userAgent,
+      'withZoom': withZoom ?? false,
+      'withLocalStorage': withLocalStorage ?? true,
+      'withLocalUrl': withLocalUrl ?? false,
+      'scrollBar': scrollBar ?? true
     };
+
+    if (headers != null) {
+      args['headers'] = headers;
+    }
+
     if (rect != null) {
-      args["rect"] = {
-        "left": rect.left,
-        "top": rect.top,
-        "width": rect.width,
-        "height": rect.height
+      args['rect'] = {
+        'left': rect.left,
+        'top': rect.top,
+        'width': rect.width,
+        'height': rect.height
       };
     }
     await _channel.invokeMethod('launch', args);
@@ -127,40 +140,48 @@ class FlutterWebviewPlugin {
 
   /// Execute Javascript inside webview
   Future<String> evalJavascript(String code) async {
-    final res = await _channel.invokeMethod('eval', {"code": code});
+    final res = await _channel.invokeMethod('eval', {'code': code});
     return res;
   }
 
   /// Close the Webview
   /// Will trigger the [onDestroy] event
-  Future close() => _channel.invokeMethod("close");
+  Future close() => _channel.invokeMethod('close');
 
   /// Reloads the WebView.
   /// This is only available on Android for now.
-  Future reload() => _channel.invokeMethod("reload");
+  Future reload() => _channel.invokeMethod('reload');
 
   /// Navigates back on the Webview.
   /// This is only available on Android for now.
-  Future goBack() => _channel.invokeMethod("back");
+  Future goBack() => _channel.invokeMethod('back');
 
   /// Navigates forward on the Webview.
   /// This is only available on Android for now.
-  Future goForward() => _channel.invokeMethod("forward");
-  
+  Future goForward() => _channel.invokeMethod('forward');
+
   // Hides the webview
-  Future hide() => _channel.invokeMethod("hide");
-  
+  Future hide() => _channel.invokeMethod('hide');
+
   // Shows the webview
-  Future show() => _channel.invokeMethod("show");
+  Future show() => _channel.invokeMethod('show');
 
   // Reload webview with a new url
   Future reloadUrl(String url) async {
-    Map<String, dynamic> args = {
-      "url": url
-    };
-    await _channel.invokeMethod("reloadUrl", args);
+    final args = <String, String>{'url': url};
+    await _channel.invokeMethod('reloadUrl', args);
   }
 
+  /// adds the plugin as ActivityResultListener
+  /// Only needed and used on Android
+  Future registerAcitivityResultListener() =>
+      _channel.invokeMethod('registerAcitivityResultListener');
+
+  /// removes the plugin as ActivityResultListener
+  /// Only needed and used on Android
+  Future removeAcitivityResultListener() =>
+      _channel.invokeMethod('removeAcitivityResultListener');
+
   /// Close all Streams
   void dispose() {
     _onDestroy.close();
@@ -168,17 +189,17 @@ class FlutterWebviewPlugin {
     _onStateChanged.close();
     _onScrollXChanged.close();
     _onScrollYChanged.close();
-    _onError.close();
+    _onHttpError.close();
     _instance = null;
   }
 
-  Future<Map<String, dynamic>> getCookies() async {
-    final cookiesString = await evalJavascript("document.cookie");
-    final cookies = {};
+  Future<Map<String, String>> getCookies() async {
+    final cookiesString = await evalJavascript('document.cookie');
+    final cookies = <String, String>{};
 
     if (cookiesString?.isNotEmpty == true) {
-      cookiesString.split(";").forEach((String cookie) {
-        final splited = cookie.split("=");
+      cookiesString.split(';').forEach((String cookie) {
+        final splited = cookie.split('=');
         cookies[splited[0]] = splited[1];
       });
     }
@@ -189,11 +210,11 @@ class FlutterWebviewPlugin {
   /// resize webview
   Future<Null> resize(Rect rect) async {
     final args = {};
-    args["rect"] = {
-      "left": rect.left,
-      "top": rect.top,
-      "width": rect.width,
-      "height": rect.height
+    args['rect'] = {
+      'left': rect.left,
+      'top': rect.top,
+      'width': rect.width,
+      'height': rect.height
     };
     await _channel.invokeMethod('resize', args);
   }
@@ -208,17 +229,24 @@ class WebViewStateChanged {
 
   factory WebViewStateChanged.fromMap(Map<String, dynamic> map) {
     WebViewState t;
-    switch (map["type"]) {
-      case "shouldStart":
+    switch (map['type']) {
+      case 'shouldStart':
         t = WebViewState.shouldStart;
         break;
-      case "startLoad":
+      case 'startLoad':
         t = WebViewState.startLoad;
         break;
-      case "finishLoad":
+      case 'finishLoad':
         t = WebViewState.finishLoad;
         break;
     }
-    return new WebViewStateChanged(t, map["url"], map["navigationType"]);
+    return new WebViewStateChanged(t, map['url'], map['navigationType']);
   }
 }
+
+class WebViewHttpError {
+  final String url;
+  final String code;
+
+  WebViewHttpError(this.code, this.url);
+}

+ 27 - 13
lib/src/webview_scaffold.dart

@@ -19,22 +19,27 @@ class WebviewScaffold extends StatefulWidget {
   final bool withZoom;
   final bool withLocalStorage;
   final bool withLocalUrl;
+  final bool scrollBar;
 
-  WebviewScaffold(
+  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.primary = true,
       this.persistentFooterButtons,
       this.bottomNavigationBar,
       this.withZoom,
       this.withLocalStorage,
-      this.withLocalUrl})
+      this.withLocalUrl,
+      this.scrollBar})
       : super(key: key);
 
   @override
@@ -46,11 +51,13 @@ class _WebviewScaffoldState extends State<WebviewScaffold> {
   Rect _rect;
   Timer _resizeTimer;
 
+  @override
   void initState() {
     super.initState();
     webviewReference.close();
   }
 
+  @override
   void dispose() {
     super.dispose();
     webviewReference.close();
@@ -62,6 +69,7 @@ class _WebviewScaffoldState extends State<WebviewScaffold> {
     if (_rect == null) {
       _rect = _buildRect(context);
       webviewReference.launch(widget.url,
+          headers: widget.headers,
           withJavascript: widget.withJavascript,
           clearCache: widget.clearCache,
           clearCookies: widget.clearCookies,
@@ -70,9 +78,10 @@ class _WebviewScaffoldState extends State<WebviewScaffold> {
           rect: _rect,
           withZoom: widget.withZoom,
           withLocalStorage: widget.withLocalStorage,
-          withLocalUrl: widget.withLocalUrl);
+          withLocalUrl: widget.withLocalUrl,
+          scrollBar: widget.scrollBar);
     } else {
-      Rect rect = _buildRect(context);
+      final rect = _buildRect(context);
       if (_rect != rect) {
         _rect = rect;
         _resizeTimer?.cancel();
@@ -86,32 +95,37 @@ class _WebviewScaffoldState extends State<WebviewScaffold> {
         appBar: widget.appBar,
         persistentFooterButtons: widget.persistentFooterButtons,
         bottomNavigationBar: widget.bottomNavigationBar,
-        body: new Center(child: new CircularProgressIndicator()));
+        body: const Center(child: const CircularProgressIndicator()));
   }
 
   Rect _buildRect(BuildContext context) {
-    bool fullscreen = widget.appBar == null;
+    final fullscreen = widget.appBar == null;
 
     final mediaQuery = MediaQuery.of(context);
     final topPadding = widget.primary ? mediaQuery.padding.top : 0.0;
-    num top =
+    final top =
         fullscreen ? 0.0 : widget.appBar.preferredSize.height + topPadding;
 
-    num height = mediaQuery.size.height - top;
+    var height = mediaQuery.size.height - top;
 
     if (widget.bottomNavigationBar != null) {
-      height -=
-          56.0 + mediaQuery.padding.bottom; // todo(lejard_h) find a way to determine bottomNavigationBar programmatically
+      height -= 56.0 +
+          mediaQuery.padding
+              .bottom; // todo(lejard_h) find a way to determine bottomNavigationBar programmatically
     }
 
     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;
+      if (widget.bottomNavigationBar == null) {
+        height -= mediaQuery.padding.bottom;
       }
     }
 
+    if (height < 0.0) {
+      height = 0.0;
+    }
+
     return new Rect.fromLTWH(0.0, top, mediaQuery.size.width, height);
   }
 }

+ 2 - 2
pubspec.yaml

@@ -5,10 +5,10 @@ authors:
 - Toufik Zitouni <toufiksapps@gmail.com>
 - Pedia <kpedia@163.com>
 homepage: https://github.com/dart-flitter/flutter_webview_plugin
-version: 0.1.6
+version: 0.2.0
 
 environment:
-  sdk: ">=1.19.0 <2.0.0"
+  sdk: ">=2.0.0-dev <3.0.0"
 
 flutter:
   plugin: