main.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. // ignore_for_file: public_member_api_docs
  5. import 'dart:async';
  6. import 'dart:convert';
  7. import 'package:flutter/material.dart';
  8. import 'package:webview_flutter/webview_flutter.dart';
  9. void main() => runApp(MaterialApp(home: WebViewExample()));
  10. const String kNavigationExamplePage = '''
  11. <!DOCTYPE html><html>
  12. <head><title>Navigation Delegate Example</title></head>
  13. <body>
  14. <p>
  15. The navigation delegate is set to block navigation to the youtube website.
  16. </p>
  17. <ul>
  18. <ul><a href="https://www.youtube.com/">https://www.youtube.com/</a></ul>
  19. <ul><a href="https://www.google.com/">https://www.google.com/</a></ul>
  20. </ul>
  21. </body>
  22. </html>
  23. ''';
  24. class WebViewExample extends StatefulWidget {
  25. @override
  26. _WebViewExampleState createState() => _WebViewExampleState();
  27. }
  28. class _WebViewExampleState extends State<WebViewExample> {
  29. final Completer<WebViewController> _controller =
  30. Completer<WebViewController>();
  31. @override
  32. Widget build(BuildContext context) {
  33. return Scaffold(
  34. appBar: AppBar(
  35. title: const Text('Flutter WebView example'),
  36. // This drop down menu demonstrates that Flutter widgets can be shown over the web view.
  37. actions: <Widget>[
  38. NavigationControls(_controller.future),
  39. SampleMenu(_controller.future),
  40. ],
  41. ),
  42. // We're using a Builder here so we have a context that is below the Scaffold
  43. // to allow calling Scaffold.of(context) so we can show a snackbar.
  44. body: Builder(builder: (BuildContext context) {
  45. return WebView(
  46. initialUrl: 'https://flutter.dev',
  47. javascriptMode: JavascriptMode.unrestricted,
  48. onWebViewCreated: (WebViewController webViewController) {
  49. _controller.complete(webViewController);
  50. },
  51. // TODO(iskakaushik): Remove this when collection literals makes it to stable.
  52. // ignore: prefer_collection_literals
  53. javascriptChannels: <JavascriptChannel>[
  54. _toasterJavascriptChannel(context),
  55. ].toSet(),
  56. navigationDelegate: (NavigationRequest request) {
  57. if (request.url.startsWith('https://www.youtube.com/')) {
  58. print('blocking navigation to $request}');
  59. return NavigationDecision.prevent;
  60. }
  61. print('allowing navigation to $request');
  62. return NavigationDecision.navigate;
  63. },
  64. onPageStarted: (String url) {
  65. print('Page started loading: $url');
  66. },
  67. onPageFinished: (String url) {
  68. print('Page finished loading: $url');
  69. },
  70. gestureNavigationEnabled: true,
  71. );
  72. }),
  73. floatingActionButton: favoriteButton(),
  74. );
  75. }
  76. JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
  77. return JavascriptChannel(
  78. name: 'Toaster',
  79. onMessageReceived: (JavascriptMessage message) {
  80. Scaffold.of(context).showSnackBar(
  81. SnackBar(content: Text(message.message)),
  82. );
  83. });
  84. }
  85. Widget favoriteButton() {
  86. return FutureBuilder<WebViewController>(
  87. future: _controller.future,
  88. builder: (BuildContext context,
  89. AsyncSnapshot<WebViewController> controller) {
  90. if (controller.hasData) {
  91. return FloatingActionButton(
  92. onPressed: () async {
  93. final String url = await controller.data.currentUrl();
  94. Scaffold.of(context).showSnackBar(
  95. SnackBar(content: Text('Favorited $url')),
  96. );
  97. },
  98. child: const Icon(Icons.favorite),
  99. );
  100. }
  101. return Container();
  102. });
  103. }
  104. }
  105. enum MenuOptions {
  106. showUserAgent,
  107. listCookies,
  108. clearCookies,
  109. addToCache,
  110. listCache,
  111. clearCache,
  112. navigationDelegate,
  113. }
  114. class SampleMenu extends StatelessWidget {
  115. SampleMenu(this.controller);
  116. final Future<WebViewController> controller;
  117. final CookieManager cookieManager = CookieManager();
  118. @override
  119. Widget build(BuildContext context) {
  120. return FutureBuilder<WebViewController>(
  121. future: controller,
  122. builder:
  123. (BuildContext context, AsyncSnapshot<WebViewController> controller) {
  124. return PopupMenuButton<MenuOptions>(
  125. onSelected: (MenuOptions value) {
  126. switch (value) {
  127. case MenuOptions.showUserAgent:
  128. _onShowUserAgent(controller.data, context);
  129. break;
  130. case MenuOptions.listCookies:
  131. _onListCookies(controller.data, context);
  132. break;
  133. case MenuOptions.clearCookies:
  134. _onClearCookies(context);
  135. break;
  136. case MenuOptions.addToCache:
  137. _onAddToCache(controller.data, context);
  138. break;
  139. case MenuOptions.listCache:
  140. _onListCache(controller.data, context);
  141. break;
  142. case MenuOptions.clearCache:
  143. _onClearCache(controller.data, context);
  144. break;
  145. case MenuOptions.navigationDelegate:
  146. _onNavigationDelegateExample(controller.data, context);
  147. break;
  148. }
  149. },
  150. itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
  151. PopupMenuItem<MenuOptions>(
  152. value: MenuOptions.showUserAgent,
  153. child: const Text('Show user agent'),
  154. enabled: controller.hasData,
  155. ),
  156. const PopupMenuItem<MenuOptions>(
  157. value: MenuOptions.listCookies,
  158. child: Text('List cookies'),
  159. ),
  160. const PopupMenuItem<MenuOptions>(
  161. value: MenuOptions.clearCookies,
  162. child: Text('Clear cookies'),
  163. ),
  164. const PopupMenuItem<MenuOptions>(
  165. value: MenuOptions.addToCache,
  166. child: Text('Add to cache'),
  167. ),
  168. const PopupMenuItem<MenuOptions>(
  169. value: MenuOptions.listCache,
  170. child: Text('List cache'),
  171. ),
  172. const PopupMenuItem<MenuOptions>(
  173. value: MenuOptions.clearCache,
  174. child: Text('Clear cache'),
  175. ),
  176. const PopupMenuItem<MenuOptions>(
  177. value: MenuOptions.navigationDelegate,
  178. child: Text('Navigation Delegate example'),
  179. ),
  180. ],
  181. );
  182. },
  183. );
  184. }
  185. void _onShowUserAgent(
  186. WebViewController controller, BuildContext context) async {
  187. // Send a message with the user agent string to the Toaster JavaScript channel we registered
  188. // with the WebView.
  189. await controller.evaluateJavascript(
  190. 'Toaster.postMessage("User Agent: " + navigator.userAgent);');
  191. }
  192. void _onListCookies(
  193. WebViewController controller, BuildContext context) async {
  194. final String cookies =
  195. await controller.evaluateJavascript('document.cookie');
  196. Scaffold.of(context).showSnackBar(SnackBar(
  197. content: Column(
  198. mainAxisAlignment: MainAxisAlignment.end,
  199. mainAxisSize: MainAxisSize.min,
  200. children: <Widget>[
  201. const Text('Cookies:'),
  202. _getCookieList(cookies),
  203. ],
  204. ),
  205. ));
  206. }
  207. void _onAddToCache(WebViewController controller, BuildContext context) async {
  208. await controller.evaluateJavascript(
  209. 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";');
  210. Scaffold.of(context).showSnackBar(const SnackBar(
  211. content: Text('Added a test entry to cache.'),
  212. ));
  213. }
  214. void _onListCache(WebViewController controller, BuildContext context) async {
  215. await controller.evaluateJavascript('caches.keys()'
  216. '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
  217. '.then((caches) => Toaster.postMessage(caches))');
  218. }
  219. void _onClearCache(WebViewController controller, BuildContext context) async {
  220. await controller.clearCache();
  221. Scaffold.of(context).showSnackBar(const SnackBar(
  222. content: Text("Cache cleared."),
  223. ));
  224. }
  225. void _onClearCookies(BuildContext context) async {
  226. final bool hadCookies = await cookieManager.clearCookies();
  227. String message = 'There were cookies. Now, they are gone!';
  228. if (!hadCookies) {
  229. message = 'There are no cookies.';
  230. }
  231. Scaffold.of(context).showSnackBar(SnackBar(
  232. content: Text(message),
  233. ));
  234. }
  235. void _onNavigationDelegateExample(
  236. WebViewController controller, BuildContext context) async {
  237. final String contentBase64 =
  238. base64Encode(const Utf8Encoder().convert(kNavigationExamplePage));
  239. await controller.loadUrl('data:text/html;base64,$contentBase64');
  240. }
  241. Widget _getCookieList(String cookies) {
  242. if (cookies == null || cookies == '""') {
  243. return Container();
  244. }
  245. final List<String> cookieList = cookies.split(';');
  246. final Iterable<Text> cookieWidgets =
  247. cookieList.map((String cookie) => Text(cookie));
  248. return Column(
  249. mainAxisAlignment: MainAxisAlignment.end,
  250. mainAxisSize: MainAxisSize.min,
  251. children: cookieWidgets.toList(),
  252. );
  253. }
  254. }
  255. class NavigationControls extends StatelessWidget {
  256. const NavigationControls(this._webViewControllerFuture)
  257. : assert(_webViewControllerFuture != null);
  258. final Future<WebViewController> _webViewControllerFuture;
  259. @override
  260. Widget build(BuildContext context) {
  261. return FutureBuilder<WebViewController>(
  262. future: _webViewControllerFuture,
  263. builder:
  264. (BuildContext context, AsyncSnapshot<WebViewController> snapshot) {
  265. final bool webViewReady =
  266. snapshot.connectionState == ConnectionState.done;
  267. final WebViewController controller = snapshot.data;
  268. return Row(
  269. children: <Widget>[
  270. IconButton(
  271. icon: const Icon(Icons.arrow_back_ios),
  272. onPressed: !webViewReady
  273. ? null
  274. : () async {
  275. if (await controller.canGoBack()) {
  276. await controller.goBack();
  277. } else {
  278. Scaffold.of(context).showSnackBar(
  279. const SnackBar(content: Text("No back history item")),
  280. );
  281. return;
  282. }
  283. },
  284. ),
  285. IconButton(
  286. icon: const Icon(Icons.arrow_forward_ios),
  287. onPressed: !webViewReady
  288. ? null
  289. : () async {
  290. if (await controller.canGoForward()) {
  291. await controller.goForward();
  292. } else {
  293. Scaffold.of(context).showSnackBar(
  294. const SnackBar(
  295. content: Text("No forward history item")),
  296. );
  297. return;
  298. }
  299. },
  300. ),
  301. IconButton(
  302. icon: const Icon(Icons.replay),
  303. onPressed: !webViewReady
  304. ? null
  305. : () {
  306. controller.reload();
  307. },
  308. ),
  309. ],
  310. );
  311. },
  312. );
  313. }
  314. }