base.dart 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import 'dart:async';
  2. import 'dart:ui';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. const _kChannel = 'flutter_webview_plugin';
  6. // TODO: more general state for iOS/android
  7. enum WebViewState { shouldStart, startLoad, finishLoad }
  8. // TODO: use an id by webview to be able to manage multiple webview
  9. /// Singleton Class that communicate with a Webview Instance
  10. /// Have to be instanciate after `runApp` called.
  11. class FlutterWebviewPlugin {
  12. final _channel = const MethodChannel(_kChannel);
  13. final _onDestroy = new StreamController<Null>.broadcast();
  14. final _onUrlChanged = new StreamController<String>.broadcast();
  15. final _onStateChanged = new StreamController<WebViewStateChanged>.broadcast();
  16. final _onScrollXChanged = new StreamController<double>.broadcast();
  17. final _onScrollYChanged = new StreamController<double>.broadcast();
  18. final _onHttpError = new StreamController<WebViewHttpError>.broadcast();
  19. static FlutterWebviewPlugin _instance;
  20. factory FlutterWebviewPlugin() => _instance ??= new FlutterWebviewPlugin._();
  21. FlutterWebviewPlugin._() {
  22. _channel.setMethodCallHandler(_handleMessages);
  23. }
  24. Future<Null> _handleMessages(MethodCall call) async {
  25. switch (call.method) {
  26. case 'onDestroy':
  27. _onDestroy.add(null);
  28. break;
  29. case 'onUrlChanged':
  30. _onUrlChanged.add(call.arguments['url']);
  31. break;
  32. case "onScrollXChanged":
  33. _onScrollXChanged.add(call.arguments["xDirection"]);
  34. break;
  35. case "onScrollYChanged":
  36. _onScrollYChanged.add(call.arguments["yDirection"]);
  37. break;
  38. case "onState":
  39. _onStateChanged.add(
  40. new WebViewStateChanged.fromMap(
  41. new Map<String, dynamic>.from(call.arguments)),
  42. );
  43. break;
  44. case 'onHttpError':
  45. _onHttpError.add(
  46. WebViewHttpError(call.arguments['code'], call.arguments['url']));
  47. break;
  48. }
  49. }
  50. /// Listening the OnDestroy LifeCycle Event for Android
  51. Stream<Null> get onDestroy => _onDestroy.stream;
  52. /// Listening url changed
  53. Stream<String> get onUrlChanged => _onUrlChanged.stream;
  54. /// Listening the onState Event for iOS WebView and Android
  55. /// content is Map for type: {shouldStart(iOS)|startLoad|finishLoad}
  56. /// more detail than other events
  57. Stream<WebViewStateChanged> get onStateChanged => _onStateChanged.stream;
  58. /// Listening web view y position scroll change
  59. Stream<double> get onScrollYChanged => _onScrollYChanged.stream;
  60. /// Listening web view x position scroll change
  61. Stream<double> get onScrollXChanged => _onScrollXChanged.stream;
  62. Stream<WebViewHttpError> get onHttpError => _onHttpError.stream;
  63. /// Start the Webview with [url]
  64. /// - [headers] specify additional HTTP headers
  65. /// - [withJavascript] enable Javascript or not for the Webview
  66. /// iOS WebView: Not implemented yet
  67. /// - [clearCache] clear the cache of the Webview
  68. /// - [clearCookies] clear all cookies of the Webview
  69. /// - [hidden] not show
  70. /// - [rect]: show in rect, fullscreen if null
  71. /// - [enableAppScheme]: false will enable all schemes, true only for httt/https/about
  72. /// android: Not implemented yet
  73. /// - [userAgent]: set the User-Agent of WebView
  74. /// - [withZoom]: enable zoom on webview
  75. /// - [withLocalStorage] enable localStorage API on Webview
  76. /// Currently Android only.
  77. /// It is always enabled in UIWebView of iOS and can not be disabled.
  78. /// - [withLocalUrl]: allow url as a local path
  79. /// Allow local files on iOs > 9.0
  80. /// - [scrollBar]: enable or disable scrollbar
  81. Future<Null> launch(String url,
  82. {Map<String, String> headers,
  83. bool withJavascript,
  84. bool clearCache,
  85. bool clearCookies,
  86. bool hidden,
  87. bool enableAppScheme,
  88. Rect rect,
  89. String userAgent,
  90. bool withZoom,
  91. bool withLocalStorage,
  92. bool withLocalUrl,
  93. bool scrollBar}) async {
  94. final args = <String, dynamic>{
  95. 'url': url,
  96. 'withJavascript': withJavascript ?? true,
  97. 'clearCache': clearCache ?? false,
  98. 'hidden': hidden ?? false,
  99. 'clearCookies': clearCookies ?? false,
  100. 'enableAppScheme': enableAppScheme ?? true,
  101. 'userAgent': userAgent,
  102. 'withZoom': withZoom ?? false,
  103. 'withLocalStorage': withLocalStorage ?? true,
  104. 'withLocalUrl': withLocalUrl ?? false,
  105. 'scrollBar': scrollBar ?? true
  106. };
  107. if (headers != null) {
  108. args['headers'] = headers;
  109. }
  110. if (rect != null) {
  111. args['rect'] = {
  112. 'left': rect.left,
  113. 'top': rect.top,
  114. 'width': rect.width,
  115. 'height': rect.height
  116. };
  117. }
  118. await _channel.invokeMethod('launch', args);
  119. }
  120. /// Execute Javascript inside webview
  121. Future<String> evalJavascript(String code) async {
  122. final res = await _channel.invokeMethod('eval', {'code': code});
  123. return res;
  124. }
  125. /// Close the Webview
  126. /// Will trigger the [onDestroy] event
  127. Future close() => _channel.invokeMethod('close');
  128. /// Reloads the WebView.
  129. /// This is only available on Android for now.
  130. Future reload() => _channel.invokeMethod('reload');
  131. /// Navigates back on the Webview.
  132. /// This is only available on Android for now.
  133. Future goBack() => _channel.invokeMethod('back');
  134. /// Navigates forward on the Webview.
  135. /// This is only available on Android for now.
  136. Future goForward() => _channel.invokeMethod('forward');
  137. // Hides the webview
  138. Future hide() => _channel.invokeMethod('hide');
  139. // Shows the webview
  140. Future show() => _channel.invokeMethod('show');
  141. // Reload webview with a new url
  142. Future reloadUrl(String url) async {
  143. final args = <String, String>{'url': url};
  144. await _channel.invokeMethod('reloadUrl', args);
  145. }
  146. // Stops current loading process
  147. Future stopLoading() => _channel.invokeMethod("stopLoading");
  148. /// adds the plugin as ActivityResultListener
  149. /// Only needed and used on Android
  150. Future registerAcitivityResultListener() =>
  151. _channel.invokeMethod('registerAcitivityResultListener');
  152. /// removes the plugin as ActivityResultListener
  153. /// Only needed and used on Android
  154. Future removeAcitivityResultListener() =>
  155. _channel.invokeMethod('removeAcitivityResultListener');
  156. /// Close all Streams
  157. void dispose() {
  158. _onDestroy.close();
  159. _onUrlChanged.close();
  160. _onStateChanged.close();
  161. _onScrollXChanged.close();
  162. _onScrollYChanged.close();
  163. _onHttpError.close();
  164. _instance = null;
  165. }
  166. Future<Map<String, String>> getCookies() async {
  167. final cookiesString = await evalJavascript('document.cookie');
  168. final cookies = <String, String>{};
  169. if (cookiesString?.isNotEmpty == true) {
  170. cookiesString.split(';').forEach((String cookie) {
  171. final splited = cookie.split('=');
  172. cookies[splited[0]] = splited[1];
  173. });
  174. }
  175. return cookies;
  176. }
  177. /// resize webview
  178. Future<Null> resize(Rect rect) async {
  179. final args = {};
  180. args['rect'] = {
  181. 'left': rect.left,
  182. 'top': rect.top,
  183. 'width': rect.width,
  184. 'height': rect.height
  185. };
  186. await _channel.invokeMethod('resize', args);
  187. }
  188. }
  189. class WebViewStateChanged {
  190. final WebViewState type;
  191. final String url;
  192. final int navigationType;
  193. WebViewStateChanged(this.type, this.url, this.navigationType);
  194. factory WebViewStateChanged.fromMap(Map<String, dynamic> map) {
  195. WebViewState t;
  196. switch (map['type']) {
  197. case 'shouldStart':
  198. t = WebViewState.shouldStart;
  199. break;
  200. case 'startLoad':
  201. t = WebViewState.startLoad;
  202. break;
  203. case 'finishLoad':
  204. t = WebViewState.finishLoad;
  205. break;
  206. }
  207. return new WebViewStateChanged(t, map['url'], map['navigationType']);
  208. }
  209. }
  210. class WebViewHttpError {
  211. final String url;
  212. final String code;
  213. WebViewHttpError(this.code, this.url);
  214. }