Created
April 16, 2020 13:44
-
-
Save loicgeek/f07684f2b01f8dfeac4c593005d4c8b4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'dart:convert'; | |
import 'package:flutter/material.dart'; | |
import 'package:webview_flutter/webview_flutter.dart'; | |
void main() => runApp(MaterialApp(home: WebViewExample())); | |
const String kNavigationExamplePage = ''' | |
<!DOCTYPE html><html> | |
<head><title>Navigation Delegate Example</title></head> | |
<body> | |
<p> | |
The navigation delegate is set to block navigation to the youtube website. | |
</p> | |
<ul> | |
<ul><a href="https://www.youtube.com/">https://www.youtube.com/</a></ul> | |
<ul><a href="https://www.google.com/">https://www.google.com/</a></ul> | |
</ul> | |
</body> | |
</html> | |
'''; | |
class WebViewExample extends StatefulWidget { | |
@override | |
_WebViewExampleState createState() => _WebViewExampleState(); | |
} | |
class _WebViewExampleState extends State<WebViewExample> { | |
final Completer<WebViewController> _controller = | |
Completer<WebViewController>(); | |
var isLoading = true; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Flutter WebView example'), | |
// This drop down menu demonstrates that Flutter widgets can be shown over the web view. | |
actions: <Widget>[ | |
NavigationControls(_controller.future), | |
SampleMenu(_controller.future), | |
], | |
), | |
// We're using a Builder here so we have a context that is below the Scaffold | |
// to allow calling Scaffold.of(context) so we can show a snackbar. | |
body: Stack( | |
children: <Widget>[ | |
Builder(builder: (BuildContext context) { | |
return WebView( | |
initialUrl: 'https://flutter.dev', | |
javascriptMode: JavascriptMode.unrestricted, | |
onWebViewCreated: (WebViewController webViewController) { | |
_controller.complete(webViewController); | |
}, | |
// TODO(iskakaushik): Remove this when collection literals makes it to stable. | |
// ignore: prefer_collection_literals | |
javascriptChannels: <JavascriptChannel>[ | |
_toasterJavascriptChannel(context), | |
_dataJavascriptChannel(context), | |
].toSet(), | |
navigationDelegate: (NavigationRequest request) { | |
if (request.url.startsWith('https://www.youtube.com/')) { | |
print('blocking navigation to $request}'); | |
return NavigationDecision.prevent; | |
} | |
print('allowing navigation to $request'); | |
return NavigationDecision.navigate; | |
}, | |
onPageStarted: (String url) { | |
print('Page started loading: $url'); | |
}, | |
onPageFinished: (String url) { | |
print('Page finished loading: $url'); | |
isLoading = false; | |
setState(() {}); | |
}, | |
gestureNavigationEnabled: true, | |
); | |
}), | |
isLoading | |
? Container(child: Center(child: CircularProgressIndicator())) | |
: Container(), | |
], | |
), | |
floatingActionButton: favoriteButton(), | |
); | |
} | |
injectJs(WebViewController controller) { | |
controller.evaluateJavascript(''' | |
var matches = document.querySelectorAll("p"); | |
alert("hello"); | |
document.querySelector('a').addEventListener("click", function(e){ | |
e.preventDefault(); | |
document.querySelector("a").innerText = "GeeksforGeeks"; | |
Transferer.postMessage(JSON.stringify({name:"loic"})); | |
}); | |
Toaster.postMessage("Number of links: " + matches.length); | |
(function(){ | |
//three JS files that need to be loaded one after the other | |
var libs = [ | |
'https://code.jquery.com/jquery-3.1.1.min.js', | |
'https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js', | |
'https://cdnjs.cloudflare.com/ajax/libs/underscore.string/3.3.4/underscore.string.js' | |
]; | |
var injectLibFromStack = function(){ | |
if(libs.length > 0){ | |
//grab the next item on the stack | |
var nextLib = libs.shift(); | |
var headTag = document.getElementsByTagName('head')[0]; | |
//create a script tag with this library | |
var scriptTag = document.createElement('script'); | |
scriptTag.src = nextLib; | |
//when successful, inject the next script | |
scriptTag.onload = function(e){ | |
Toaster.postMessage("---> loaded: " + e.target.src); | |
injectLibFromStack(); | |
}; | |
//append the script tag to the <head></head> | |
headTag.appendChild(scriptTag); | |
console.log("injecting: " + nextLib); | |
} | |
else return; | |
} | |
//start script injection | |
injectLibFromStack(); | |
})(); | |
'''); | |
print('inject js called'); | |
} | |
JavascriptChannel _toasterJavascriptChannel(BuildContext context) { | |
return JavascriptChannel( | |
name: 'Toaster', | |
onMessageReceived: (JavascriptMessage message) { | |
Scaffold.of(context).showSnackBar( | |
SnackBar(content: Text(message.message)), | |
); | |
}); | |
} | |
JavascriptChannel _dataJavascriptChannel(BuildContext context) { | |
return JavascriptChannel( | |
name: 'Transferer', | |
onMessageReceived: (JavascriptMessage message) { | |
print(jsonDecode(message.message)); | |
}); | |
} | |
Widget favoriteButton() { | |
return FutureBuilder<WebViewController>( | |
future: _controller.future, | |
builder: (BuildContext context, | |
AsyncSnapshot<WebViewController> controller) { | |
if (controller.hasData) { | |
injectJs(controller.data); | |
return Container(); | |
} | |
return Container(); | |
}); | |
} | |
} | |
enum MenuOptions { | |
showUserAgent, | |
listCookies, | |
clearCookies, | |
addToCache, | |
listCache, | |
clearCache, | |
navigationDelegate, | |
} | |
class SampleMenu extends StatelessWidget { | |
SampleMenu(this.controller); | |
final Future<WebViewController> controller; | |
final CookieManager cookieManager = CookieManager(); | |
@override | |
Widget build(BuildContext context) { | |
return FutureBuilder<WebViewController>( | |
future: controller, | |
builder: | |
(BuildContext context, AsyncSnapshot<WebViewController> controller) { | |
return PopupMenuButton<MenuOptions>( | |
onSelected: (MenuOptions value) { | |
switch (value) { | |
case MenuOptions.showUserAgent: | |
_onShowUserAgent(controller.data, context); | |
break; | |
case MenuOptions.listCookies: | |
_onListCookies(controller.data, context); | |
break; | |
case MenuOptions.clearCookies: | |
_onClearCookies(context); | |
break; | |
case MenuOptions.addToCache: | |
_onAddToCache(controller.data, context); | |
break; | |
case MenuOptions.listCache: | |
_onListCache(controller.data, context); | |
break; | |
case MenuOptions.clearCache: | |
_onClearCache(controller.data, context); | |
break; | |
case MenuOptions.navigationDelegate: | |
_onNavigationDelegateExample(controller.data, context); | |
break; | |
} | |
}, | |
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[ | |
PopupMenuItem<MenuOptions>( | |
value: MenuOptions.showUserAgent, | |
child: const Text('Show user agent'), | |
enabled: controller.hasData, | |
), | |
const PopupMenuItem<MenuOptions>( | |
value: MenuOptions.listCookies, | |
child: Text('List cookies'), | |
), | |
const PopupMenuItem<MenuOptions>( | |
value: MenuOptions.clearCookies, | |
child: Text('Clear cookies'), | |
), | |
const PopupMenuItem<MenuOptions>( | |
value: MenuOptions.addToCache, | |
child: Text('Add to cache'), | |
), | |
const PopupMenuItem<MenuOptions>( | |
value: MenuOptions.listCache, | |
child: Text('List cache'), | |
), | |
const PopupMenuItem<MenuOptions>( | |
value: MenuOptions.clearCache, | |
child: Text('Clear cache'), | |
), | |
const PopupMenuItem<MenuOptions>( | |
value: MenuOptions.navigationDelegate, | |
child: Text('Navigation Delegate example'), | |
), | |
], | |
); | |
}, | |
); | |
} | |
void _onShowUserAgent( | |
WebViewController controller, BuildContext context) async { | |
// Send a message with the user agent string to the Toaster JavaScript channel we registered | |
// with the WebView. | |
await controller.evaluateJavascript( | |
'Toaster.postMessage("User Agent: " + navigator.userAgent);'); | |
} | |
void _onListCookies( | |
WebViewController controller, BuildContext context) async { | |
final String cookies = | |
await controller.evaluateJavascript('document.cookie'); | |
Scaffold.of(context).showSnackBar(SnackBar( | |
content: Column( | |
mainAxisAlignment: MainAxisAlignment.end, | |
mainAxisSize: MainAxisSize.min, | |
children: <Widget>[ | |
const Text('Cookies:'), | |
_getCookieList(cookies), | |
], | |
), | |
)); | |
} | |
void _onAddToCache(WebViewController controller, BuildContext context) async { | |
await controller.evaluateJavascript( | |
'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";'); | |
Scaffold.of(context).showSnackBar(const SnackBar( | |
content: Text('Added a test entry to cache.'), | |
)); | |
} | |
void _onListCache(WebViewController controller, BuildContext context) async { | |
await controller.evaluateJavascript('caches.keys()' | |
'.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' | |
'.then((caches) => Toaster.postMessage(caches))'); | |
} | |
void _onClearCache(WebViewController controller, BuildContext context) async { | |
await controller.clearCache(); | |
Scaffold.of(context).showSnackBar(const SnackBar( | |
content: Text("Cache cleared."), | |
)); | |
} | |
void _onClearCookies(BuildContext context) async { | |
final bool hadCookies = await cookieManager.clearCookies(); | |
String message = 'There were cookies. Now, they are gone!'; | |
if (!hadCookies) { | |
message = 'There are no cookies.'; | |
} | |
Scaffold.of(context).showSnackBar(SnackBar( | |
content: Text(message), | |
)); | |
} | |
void _onNavigationDelegateExample( | |
WebViewController controller, BuildContext context) async { | |
final String contentBase64 = | |
base64Encode(const Utf8Encoder().convert(kNavigationExamplePage)); | |
await controller.loadUrl('data:text/html;base64,$contentBase64'); | |
} | |
Widget _getCookieList(String cookies) { | |
if (cookies == null || cookies == '""') { | |
return Container(); | |
} | |
final List<String> cookieList = cookies.split(';'); | |
final Iterable<Text> cookieWidgets = | |
cookieList.map((String cookie) => Text(cookie)); | |
return Column( | |
mainAxisAlignment: MainAxisAlignment.end, | |
mainAxisSize: MainAxisSize.min, | |
children: cookieWidgets.toList(), | |
); | |
} | |
} | |
class NavigationControls extends StatelessWidget { | |
const NavigationControls(this._webViewControllerFuture) | |
: assert(_webViewControllerFuture != null); | |
final Future<WebViewController> _webViewControllerFuture; | |
@override | |
Widget build(BuildContext context) { | |
return FutureBuilder<WebViewController>( | |
future: _webViewControllerFuture, | |
builder: | |
(BuildContext context, AsyncSnapshot<WebViewController> snapshot) { | |
final bool webViewReady = | |
snapshot.connectionState == ConnectionState.done; | |
final WebViewController controller = snapshot.data; | |
return Row( | |
children: <Widget>[ | |
IconButton( | |
icon: const Icon(Icons.arrow_back_ios), | |
onPressed: !webViewReady | |
? null | |
: () async { | |
if (await controller.canGoBack()) { | |
await controller.goBack(); | |
} else { | |
Scaffold.of(context).showSnackBar( | |
const SnackBar(content: Text("No back history item")), | |
); | |
return; | |
} | |
}, | |
), | |
IconButton( | |
icon: const Icon(Icons.arrow_forward_ios), | |
onPressed: !webViewReady | |
? null | |
: () async { | |
if (await controller.canGoForward()) { | |
await controller.goForward(); | |
} else { | |
Scaffold.of(context).showSnackBar( | |
const SnackBar( | |
content: Text("No forward history item")), | |
); | |
return; | |
} | |
}, | |
), | |
IconButton( | |
icon: const Icon(Icons.replay), | |
onPressed: !webViewReady | |
? null | |
: () { | |
controller.reload(); | |
}, | |
), | |
], | |
); | |
}, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment