webview_scaffold.dart 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import 'dart:async';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/rendering.dart';
  5. import 'base.dart';
  6. class WebviewScaffold extends StatefulWidget {
  7. const WebviewScaffold({
  8. Key key,
  9. this.appBar,
  10. @required this.url,
  11. this.headers,
  12. this.withJavascript,
  13. this.clearCache,
  14. this.clearCookies,
  15. this.enableAppScheme,
  16. this.userAgent,
  17. this.primary = true,
  18. this.persistentFooterButtons,
  19. this.bottomNavigationBar,
  20. this.withZoom,
  21. this.displayZoomControls,
  22. this.withLocalStorage,
  23. this.withLocalUrl,
  24. this.withOverviewMode,
  25. this.useWideViewPort,
  26. this.scrollBar,
  27. this.supportMultipleWindows,
  28. this.appCacheEnabled,
  29. this.hidden = false,
  30. this.initialChild,
  31. this.allowFileURLs,
  32. this.resizeToAvoidBottomInset = false,
  33. this.invalidUrlRegex,
  34. this.geolocationEnabled,
  35. this.debuggingEnabled = false,
  36. }) : super(key: key);
  37. final PreferredSizeWidget appBar;
  38. final String url;
  39. final Map<String, String> headers;
  40. final bool withJavascript;
  41. final bool clearCache;
  42. final bool clearCookies;
  43. final bool enableAppScheme;
  44. final String userAgent;
  45. final bool primary;
  46. final List<Widget> persistentFooterButtons;
  47. final Widget bottomNavigationBar;
  48. final bool withZoom;
  49. final bool displayZoomControls;
  50. final bool withLocalStorage;
  51. final bool withLocalUrl;
  52. final bool scrollBar;
  53. final bool supportMultipleWindows;
  54. final bool appCacheEnabled;
  55. final bool hidden;
  56. final Widget initialChild;
  57. final bool allowFileURLs;
  58. final bool resizeToAvoidBottomInset;
  59. final String invalidUrlRegex;
  60. final bool geolocationEnabled;
  61. final bool withOverviewMode;
  62. final bool useWideViewPort;
  63. final bool debuggingEnabled;
  64. @override
  65. _WebviewScaffoldState createState() => _WebviewScaffoldState();
  66. }
  67. class _WebviewScaffoldState extends State<WebviewScaffold> {
  68. final webviewReference = FlutterWebviewPlugin();
  69. Rect _rect;
  70. Timer _resizeTimer;
  71. StreamSubscription<WebViewStateChanged> _onStateChanged;
  72. var _onBack;
  73. @override
  74. void initState() {
  75. super.initState();
  76. webviewReference.close();
  77. _onBack = webviewReference.onBack.listen((_) async {
  78. if (!mounted) return;
  79. // The willPop/pop pair here is equivalent to Navigator.maybePop(),
  80. // which is what's called from the flutter back button handler.
  81. final pop = await _topMostRoute.willPop();
  82. if (pop == RoutePopDisposition.pop) {
  83. // Close the webview if it's on the route at the top of the stack.
  84. final isOnTopMostRoute = _topMostRoute == ModalRoute.of(context);
  85. if (isOnTopMostRoute) {
  86. webviewReference.close();
  87. }
  88. Navigator.pop(context);
  89. }
  90. });
  91. if (widget.hidden) {
  92. _onStateChanged =
  93. webviewReference.onStateChanged.listen((WebViewStateChanged state) {
  94. if (state.type == WebViewState.finishLoad) {
  95. webviewReference.show();
  96. }
  97. });
  98. }
  99. }
  100. /// Equivalent to [Navigator.of(context)._history.last].
  101. Route<dynamic> get _topMostRoute {
  102. var topMost;
  103. Navigator.popUntil(context, (route) {
  104. topMost = route;
  105. return true;
  106. });
  107. return topMost;
  108. }
  109. @override
  110. void dispose() {
  111. super.dispose();
  112. _onBack?.cancel();
  113. _resizeTimer?.cancel();
  114. webviewReference.close();
  115. if (widget.hidden) {
  116. _onStateChanged.cancel();
  117. }
  118. webviewReference.dispose();
  119. }
  120. @override
  121. Widget build(BuildContext context) {
  122. return Scaffold(
  123. appBar: widget.appBar,
  124. resizeToAvoidBottomInset: widget.resizeToAvoidBottomInset,
  125. persistentFooterButtons: widget.persistentFooterButtons,
  126. bottomNavigationBar: widget.bottomNavigationBar,
  127. body: _WebviewPlaceholder(
  128. onRectChanged: (Rect value) {
  129. if (_rect == null) {
  130. _rect = value;
  131. webviewReference.launch(
  132. widget.url,
  133. headers: widget.headers,
  134. withJavascript: widget.withJavascript,
  135. clearCache: widget.clearCache,
  136. clearCookies: widget.clearCookies,
  137. hidden: widget.hidden,
  138. enableAppScheme: widget.enableAppScheme,
  139. userAgent: widget.userAgent,
  140. rect: _rect,
  141. withZoom: widget.withZoom,
  142. displayZoomControls: widget.displayZoomControls,
  143. withLocalStorage: widget.withLocalStorage,
  144. withLocalUrl: widget.withLocalUrl,
  145. withOverviewMode: widget.withOverviewMode,
  146. useWideViewPort: widget.useWideViewPort,
  147. scrollBar: widget.scrollBar,
  148. supportMultipleWindows: widget.supportMultipleWindows,
  149. appCacheEnabled: widget.appCacheEnabled,
  150. allowFileURLs: widget.allowFileURLs,
  151. invalidUrlRegex: widget.invalidUrlRegex,
  152. geolocationEnabled: widget.geolocationEnabled,
  153. debuggingEnabled: widget.debuggingEnabled,
  154. );
  155. } else {
  156. if (_rect != value) {
  157. _rect = value;
  158. _resizeTimer?.cancel();
  159. _resizeTimer = Timer(const Duration(milliseconds: 250), () {
  160. // avoid resizing to fast when build is called multiple time
  161. webviewReference.resize(_rect);
  162. });
  163. }
  164. }
  165. },
  166. child: widget.initialChild ?? const Center(child: const CircularProgressIndicator()),
  167. ),
  168. );
  169. }
  170. }
  171. class _WebviewPlaceholder extends SingleChildRenderObjectWidget {
  172. const _WebviewPlaceholder({
  173. Key key,
  174. @required this.onRectChanged,
  175. Widget child,
  176. }) : super(key: key, child: child);
  177. final ValueChanged<Rect> onRectChanged;
  178. @override
  179. RenderObject createRenderObject(BuildContext context) {
  180. return _WebviewPlaceholderRender(
  181. onRectChanged: onRectChanged,
  182. );
  183. }
  184. @override
  185. void updateRenderObject(BuildContext context, _WebviewPlaceholderRender renderObject) {
  186. renderObject..onRectChanged = onRectChanged;
  187. }
  188. }
  189. class _WebviewPlaceholderRender extends RenderProxyBox {
  190. _WebviewPlaceholderRender({
  191. RenderBox child,
  192. ValueChanged<Rect> onRectChanged,
  193. }) : _callback = onRectChanged,
  194. super(child);
  195. ValueChanged<Rect> _callback;
  196. Rect _rect;
  197. Rect get rect => _rect;
  198. set onRectChanged(ValueChanged<Rect> callback) {
  199. if (callback != _callback) {
  200. _callback = callback;
  201. notifyRect();
  202. }
  203. }
  204. void notifyRect() {
  205. if (_callback != null && _rect != null) {
  206. _callback(_rect);
  207. }
  208. }
  209. @override
  210. void paint(PaintingContext context, Offset offset) {
  211. super.paint(context, offset);
  212. final rect = offset & size;
  213. if (_rect != rect) {
  214. _rect = rect;
  215. notifyRect();
  216. }
  217. }
  218. }