base.dart 8.1 KB

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