webview_scaffold.dart 6.2 KB

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