1676317440
Webview is a tiny cross-platform library to make web-based GUIs for desktop applications.
⚠️ This project is still in development. Expect breaking changes.
import { Webview } from "https://deno.land/x/webview/mod.ts";
const html = `
<html>
<body>
<h1>Hello from deno v${Deno.version.deno}</h1>
</body>
</html>
`;
const webview = new Webview();
webview.navigate(`data:text/html,${encodeURIComponent(html)}`);
webview.run();
You can run this example directly from the web:
deno run -Ar --unstable https://deno.land/x/webview/examples/local.ts
or in your development environment:
deno run -Ar --unstable examples/local.ts
you can find other examples in the examples/
directory.
You can find the official documentation here.
sudo apt-get install libwebkit2gtk-4.0-dev
)Make sure to init the webview submodule with:
$ git submodule update --init --recursive
Building on Windows requires admin privileges.
$ deno task build
To run webview_deno without automatically downloading the binaries from releases you will need to use the environment variable PLUGIN_URL
and set it to the path where the built binaries are located. This is usually file://./target/release
.
$ deno task build
$ PLUGIN_URL=./build/
$ deno run --unstable -A examples/local.ts
or
$ deno task run examples/local.ts
or if you have the webview library already built and didn't make any changes to it, you can skip the building step with:
$ deno task run:fast examples/local.ts
PLUGIN_URL
- Set a custom library URL. Defaults to the latest release assets on Github. Setting this also disables cache for plug
.Pull request, issues and feedback are very welcome. Code style is formatted with deno task fmt
, linted with deno task lint
and commit messages are done following Conventional Commits spec.
Author: Webview
Source Code: https://github.com/webview/webview_deno
License: MIT license
1672421100
Create a custom, lightweight macOS app from a group of websites, complete with:
Watch me create a Slack clone from scratch in 30 seconds (high res video):
I've also written a few blog posts that discuss some of the decisions behind Multi:
The easiest method is to use Homebrew:
brew install --cask multi
Alternatively, you can manually download and run the latest .dmg
from Releases. If you are on macOS 10.13 High Sierra (the minimum supported version), you'll also need to install the Swift runtime from Apple.
Multi apps store their configuration in a single JSON file. If your app is named Test
, then you'll find that file at /Applications/Multi/Test.app/Contents/Resources/config.json
. The JSON configuration uses the following top-level fields:
Field Name | Type | Description |
---|---|---|
tabs | Array (Required) | Titles and URLs of tabs for this app |
windowed | Boolean (Optional, default false ) | Start the app with each tab in its own window |
alwaysNotify | Boolean (Optional, default false ) | Show macOS notifications even if your app is currently focused |
openNewWindowsWith | String (Optional, macOS 10.15+) | Override system default browser for external links — value is a bundle identifier like com.apple.Safari , com.google.Chrome , or com.mozilla.firefox |
openNewWindowsInBackground | Boolean (Optional, default false , macOS 10.15+) | Determines if browser app becomes active when opening external links |
The tabs
field is an array of objects with the following fields:
Field Name | Type | Description |
---|---|---|
title | String (Required) | Whatever you want to call this tab |
url | String (Required) | Starting page for this tab |
customJs | Array of Strings (Optional) | Custom JS URLs (see Custom JS/CSS) |
customCss | Array of Strings (Optional) | Custom CSS URLs (see Custom JS/CSS) |
basicAuthUser | String (Optional) | User name credential for requests that use basic access authentication |
basicAuthPassword | String (Optional) | Password credential for requests that use basic access authentication |
userAgent | String (Optional) | Override the default WebKit user agent header |
Here's the bare minimum example used in the Slack demo video above:
{ "tabs": [{ "title": "Slack Lite", "url": "https://app.slack.com/client" }] }
Here's a fancier example that uses the optional fields referenced above:
{
"tabs": [
{
"title": "Dancing",
"url": "https://rc.kofi.sexy/bathroom-floss",
"basicAuthUser": "user",
"basicAuthPassword": "password",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15"
},
{
"title": "Walking",
"url": "https://kofi.sexy/cel-shading",
"customJs": [ "https://raw.githubusercontent.com/kofigumbs/multi/2.x/Assets/test.js" ],
"customCss": [ "https://raw.githubusercontent.com/kofigumbs/multi/2.x/Assets/test.css" ]
}
],
"windowed": true,
"alwaysNotify": true,
"openNewWindowsWith": "com.apple.Safari",
"openNewWindowsInBackground": true
}
If your configuration file fails to decode for any reason, your Multi app will open to the preferences window, where you can fix any issues.
Before v2.2.0, Multi included a hard-coded user-agent that made it appear like Safari. This behavior caused subtle issues and confusion when the hard-coded user-agent didn't reflect the system WebKit version. Recent Multi versions remove the hard-coded user-agent, but now sites like Slack and WhatsApp complain that your browser is out of date. (Ideally these sites would use feature detection instead of user-agent sniffing to gracefully degrade behavior; alas, the world does not work ideally.)
If your site doesn't work because it thinks you're using an outdated browser, try setting the userAgent
config to match your Safari version.
create-mac-app
You can create and update Multi apps entirely from the command-line with the included script. In fact, the Multi configuration UI just runs this script under-the-hood! The create-mac-app
script takes its options as environment variables. For instance, here's how you'd create a bare-minimum app named Test
:
MULTI_APP_NAME='Test' /Applications/Multi.app/Contents/Resources/create-mac-app
When you open Test
, you'll be greeted with the preferences window, where you can finish configuring your app. If you'd like to configure your app entirely from the command-line, you can set any of the following variables:
MULTI_ICON_PATH | PNG or ICNS path to icon image |
MULTI_JSON_CONFIG | See JSON configuration |
MULTI_OVERWRITE | Set to 1 to replace an existing Multi app with the same name |
Multi lets you customize any site by injecting JavaScript and CSS on every page in your app. Each custom JS/CSS file is specified with a URL, which gives you a few options for how you want to manage your customizations:
https://raw.githubusercontent.com/kofigumbs/dotfiles/master/example.js
file:///Users/kofi/workspace/dotfiles/example.js
data:,console.log%28%27Hello%2C%20from%20Multi%21%27%29%3B%0A
Custom JS/CSS is one of the most important parts of Multi. It lets the main project stay small and focused, while letting you extend it with new features that fit your use case. If you have a neat JS/CSS snippet, you'd like to share, please open an Issue or Pull Request! Here are a few that have come up before:
Google seems to be doing some trickery here. Instead of allowing the browser to handle the links, they use JS to open a blank popup window, then dynamically set the URL to google.com/url?q=REAL_URL_HERE. Presumably all of this is so that they can track you for a few moments on your way out of their app. Custom JS solution:
(() => {
const listener = e => e.stopPropagation();
const query = () => document.querySelectorAll('a[target=_blank]').forEach(a => {
a.removeEventListener('click', listener);
a.addEventListener('click', listener, true);
});
query();
setInterval(query, 400); // wait time between DOM queries, in milliseconds
})();
Sometimes Slack's WebSocket disconnects and stops loading new messages. It seems like this is either an issue with WebKit or Slack.com. Custom JS solution:
setInterval(() => {
if (document.body.innerText.includes('Load new messages.'))
window.location.reload();
}, 90000);
Multi doesn't include any search functionality (Cmd-F). Custom JS solution:
(() => {
const highlightResults = (text, color) => {
document.designMode = "on"; // https://stackoverflow.com/a/5887719
var selection = window.getSelection();
selection.collapse(document.body, 0);
while (window.find(text)) {
document.execCommand("HiliteColor", false, color);
selection.collapseToEnd();
}
document.designMode = "off";
};
let mostRecentSearchText = "";
const search = text => {
highlightResults(mostRecentSearchText, "transparent");
highlightResults(text, "rgb(255 255 1 / 50%)");
mostRecentSearchText = text;
};
const input = document.createElement("input");
input.placeholder = "Search...";
input.style.padding = "10px 15px";
input.style.fontSize = "15px";
input.style.borderRadius = "3px";
input.style.border = "solid 1px lightgray";
const form = document.createElement("form");
form.style.display = "none";
form.style.position = "fixed";
form.style.top = "15px";
form.style.right = "15px";
form.style.zIndex = "2147483647"; // https://stackoverflow.com/a/856569
form.addEventListener("submit", e => {
e.preventDefault();
search(input.value);
});
const close = document.createElement("a");
close.innerText = "⨯";
close.href = "javascript:void(0)";
close.style.fontSize = "30px";
close.style.padding = "15px";
close.style.textDecoration = "none";
close.addEventListener("click", e => {
e.preventDefault();
search("");
form.style.display = "none";
});
form.appendChild(input);
form.appendChild(close);
document.body.appendChild(form);
document.addEventListener("keydown", event => {
if (event.metaKey && event.key === "f") {
event.preventDefault();
form.style.display = "block";
input.focus();
}
});
})();
Sometimes you have a URL outside of Multi (maybe in an email), and you want to open it in Multi. Custom JS solution:
document.addEventListener("dragover", e => e.preventDefault());
Multi doesn't include any hover-to-preview-link-target functionality. Custom CSS solution:
a:hover::after {
content: attr(href);
position: fixed;
left: 4px;
bottom: 4px;
padding: 4px;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont;
font-weight: normal;
color: black;
background: ghostwhite;
border: solid 1px black;
border-radius: 1px;
}
Multi's shortcuts should basically match those of other macOS apps:
⌘X | Cut | ⌘[ | Back | |
⌘C | Copy | ⌘] | Forward | |
⌘V | Paste | ⌘R | Reload This Page | |
⌘↑V | Paste and Match Style | ⌘+ /⌘- /⌘0 | Zoom in/out/default | |
⌘A | Select All | ^Tab | Select Next Tab | |
⌘^F | Toggle Full Screen | ^↑Tab | Select Previous Tab | |
⌘M | Minimize | ⌘1 - ⌘9 | Select Tab | |
⌘H | Hide | ⌘L | Copy current URL | |
⌘W | Close Tab | ⌘↑T | Toggle Tab Bar | |
⌘Q | Quit | ⌘↑\ | Toggle Tab Overview |
Author: Kofigumbs
Source Code: https://github.com/kofigumbs/multi
License: GPL-3.0 license
1667310793
ESPullToRefresh is an easy-to-use component that give pull-to-refresh and infinite-scrolling implemention for developers. By extension to UIScrollView, you can easily add pull-to-refresh and infinite-scrolling for any subclass of UIScrollView. If you want to customize its UI style, you just need conform the specified protocol.
UIScrollView
and its subclasses UICollectionView
UITableView
UITextView
Download and run the ESPullToRefreshExample project in Xcode to see ESPullToRefresh in action.
pod "ESPullToRefresh"
github "eggswift/pull-to-refresh"
git clone https://github.com/eggswift/pull-to-refresh.git
open ESPullToRefresh
Add ESPullToRefresh
to your project
import ESPullToRefresh
Add default pull-to-refresh
self.tableView.es.addPullToRefresh {
[unowned self] in
/// Do anything you want...
/// ...
/// Stop refresh when your job finished, it will reset refresh footer if completion is true
self.tableView.es.stopPullToRefresh(completion: true)
/// Set ignore footer or not
self.tableView.es.stopPullToRefresh(completion: true, ignoreFooter: false)
}
Add default infinite-scrolling
self.tableView.es.addInfiniteScrolling {
[unowned self] in
/// Do anything you want...
/// ...
/// If common end
self.tableView.es.stopLoadingMore()
/// If no more data
self.tableView.es.noticeNoMoreData()
}
PS: Load effect is from MeiTuan iOS app.
Customize refresh need conform the ESRefreshProtocol and ESRefreshAnimatorProtocol protocol.
Add customize pull-to-refresh
func es.addPullToRefresh(animator animator: protocol<ESRefreshProtocol, ESRefreshAnimatorProtocol>, handler: ESRefreshHandler)
Add customize infinite-scrolling
func es.addInfiniteScrolling(animator animator: protocol<ESRefreshProtocol, ESRefreshAnimatorProtocol>, handler: ESRefreshHandler)
ESPullToRefresh support for the latest expiration time and the cache refresh time, You need set an refreshIdentifier
to your UIScrollView.
scrollView.refreshIdentifier = "Your Identifier" // Set refresh identifier
scrollView.expriedTimeInterval = 20.0 // Set the expiration interval
You can use es.autoPullToRefresh()
method, when the time over the last refresh interval expires automatically refreshed.
scrollView.es.autoPullToRefresh()
let expried = scrollView.espried // expired or not
func es.removeRefreshHeader()
func es.removeRefreshFooter()
You can support the project by checking out our sponsor page. It takes only one click:
This advert was placed by GitAds
Author: Eggswift
Source Code: https://github.com/eggswift/pull-to-refresh
License: MIT license
1667021418
First, add flutter_dojah_financial as a dependency in your pubspec.yaml
file.
Add the following keys to your Info.plist file, located in <project root>/ios/Runner/Info.plist
:
NSCameraUsageDescription
- describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor.
NSMicrophoneUsageDescription
- describe why your app needs access to the microphone, if you intend to record videos. This is called Privacy - Microphone Usage Description in the visual editor.
NSLocationWhenInUseUsageDescription
- describe why your app needs access to the location, if you intend to verify address/location. This is called Privacy - Location Usage Description in the visual editor.
Kindly include this in Podfile set up.
dart: PermissionGroup.camera PERMISSION_CAMERA=1
,
dart: PermissionGroup.microphone PERMISSION_MICROPHONE=1
,
dart: PermissionGroup.location PERMISSION_LOCATION=1
,
// Add the camera permission:
<uses-permission android:name="android.permission.CAMERA" />
// Add the modify audio settings permission:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
// Add the Internet settings permission:
<uses-permission android:name="android.permission.INTERNET"/>
// Add the Location settings permission :
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
TODO: Include short and useful examples for package users. Add longer examples to /example
folder.
final Map<String,dynamic> config = {
debug: true,
otp: true, //for verification type
selfie: true //for verification type
};
final DojahFinancial _dojahFinancial = DojahFinancial(
appId: 'xxxxxxxxxxxxxxx',
publicKey: 'prod_pk_xxxxxxxxxxxxxx',
type: 'liveness' //link, identification, verification, payment
config: config
referenceId: referenceId,
);
_dojahFinancial.open(context, onSuccess: (result) {
print('$result');
},
onClose: (close) => print('Widget Closed'),
onError: (err) {
print('error: $err');
});
REMEMBER TO CHANGE THE APP ID and PUBLIC KEY WHEN DEPLOYING TO A LIVE (PRODUCTION) ENVIRONMENT
git checkout -b feature/feature-name
git commit -am 'Some commit message'
git push origin feature/feature-name
Contact Dojah for more options for the config object.
Run this command:
With Flutter:
$ flutter pub add flutter_dojah_financial
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
flutter_dojah_financial: ^0.1.5
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:flutter_dojah_financial/flutter_dojah_financial.dart';
Download Details:
Author: dojah-inc
Source Code: https://github.com/dojah-inc/flutter-financial
1664698665
webview_flutter_plus
Contents
About
webview_flutter_plus is a powerful extension of webview_flutter. This package helps to load Local HTML, CSS and Javascript content from Assets or Strings. This inherits all features of webview_flutter with minor API changes.
Do check flutter_tex a powerful implementation of this package.
What's unique in webview_flutter_plus
WebviewPlus
widget even in list view, see example.How to use?
1: Add this to your package's pubspec.yaml file:
dependencies:
webview_flutter_plus: ^0.3.0+2
2: You can install packages from the command line:
$ flutter packages get
Alternatively, your editor might support flutter packages get. Check the docs for your editor to learn more.
3: Now you need to put the following implementations in Android
and iOS
respectively.
Make sure to add this line android:usesCleartextTraffic="true"
in your <project-directory>/android/app/src/main/AndroidManifest.xml
under application
like this.
<application
android:usesCleartextTraffic="true">
</application>
Required Permissions are:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
Add following code in your <project-directory>/ios/Runner/Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key> <true/>
</dict>
<key>io.flutter.embedded_views_preview</key> <true/>
4: Now in your Dart code, you can use:
import 'package:webview_flutter_plus/webview_flutter_plus.dart';
5: Now you can use WebViewPlus as a widget:
Examples
WebViewPlus(
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
controller.loadString(r"""
<html lang="en">
<body>hello world</body>
</html>
""");
},
)
It is mandatory to mention all associated HTML, CSS and Javascript files in pubspecs.yaml
under assets:
WebViewPlus(
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
controller.loadUrl('assets/index.html');
},
)
WebViewPlusController
also allows you to get WebViewPlus
height like controller.getHeight()
WebViewPlusController _controller;
double _height = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView Example'),
),
body: ListView(
children: [
SizedBox(
height: _height,
child: WebViewPlus(
onWebViewCreated: (controller) {
this._controller = controller;
controller.loadUrl('assets/index.html');
},
onPageFinished: (url) {
_controller.getHeight().then((double height) {
print("Height: " + height.toString());
setState(() {
_height = height;
});
});
},
javascriptMode: JavascriptMode.unrestricted,
),
)
],
),
);
}
Plus APIs
WebViewPlusController controller;
controller.loadUrl('path/to/index.html')
load HTML content from Assets.controller.loadString(r"<html>HTML, CSS and Javascript code in raw string</html>");
load HTML, CSS and Javascript Code from a String.controller.getHeight()
returns height of WebViewPlus.API differences from webview_flutter
There are very minor API differences as following.
webview_flutter | webview_flutter_plus |
---|---|
WebView | WebViewPlus |
WebViewController | WebViewPlusController contains WebViewController inside. |
WebViewCreatedCallback | WebViewPlusCreatedCallback |
Rest everything is same as webview_flutter.
Run this command:
With Flutter:
$ flutter pub add webview_flutter_plus
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
webview_flutter_plus: ^0.3.0+2
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:webview_flutter_plus/webview_flutter_plus.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter_plus/webview_flutter_plus.dart';
void main() {
runApp(const WebViewPlusExample());
}
class WebViewPlusExample extends StatelessWidget {
const WebViewPlusExample({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: WebViewPlusExampleMainPage(),
);
}
}
class WebViewPlusExampleMainPage extends StatefulWidget {
const WebViewPlusExampleMainPage({Key? key}) : super(key: key);
@override
_WebViewPlusExampleMainPageState createState() =>
_WebViewPlusExampleMainPageState();
}
class _WebViewPlusExampleMainPageState
extends State<WebViewPlusExampleMainPage> {
WebViewPlusController? _controller;
double _height = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('webview_flutter_plus Example'),
),
body: ListView(
children: [
Text("Height of WebviewPlus: $_height",
style: const TextStyle(fontWeight: FontWeight.bold)),
SizedBox(
height: _height,
child: WebViewPlus(
serverPort: 5353,
javascriptChannels: null,
initialUrl: 'assets/index.html',
onWebViewCreated: (controller) {
_controller = controller;
},
onPageFinished: (url) {
_controller?.getHeight().then((double height) {
debugPrint("Height: " + height.toString());
setState(() {
_height = height;
});
});
},
javascriptMode: JavascriptMode.unrestricted,
),
)
],
),
);
}
}
Download Details:
Author: shah-xad
Source Code: https://github.com/shah-xad/webview_flutter_plus
1664553228
desktop_webview_window
Show a webview window on your flutter deksktop application.
modify your main
method.
import 'package:desktop_webview_window/desktop_webview_window.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Add this your main method.
// used to show a webview title bar.
if (runWebViewTitleBarWidget(args)) {
return;
}
runApp(MyApp());
}
launch WebViewWindow
final webview = await WebviewWindow.create();
webview.launch("https://example.com");
sudo apt install webkit2gtk-4.0
The backend of desktop_webview_window on Windows is WebView2, which requires WebView2 Runtime installed.
WebView2 Runtime is ship in box with Windows11, but it may not installed on Windows10 devices. So you need consider how to distribute the runtime to your users.
See more: https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution
For convenience, you can use WebviewWindow.isWebviewAvailable()
check whether the WebView2 is available.
see LICENSE
Run this command:
With Flutter:
$ flutter pub add desktop_webview_window
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
desktop_webview_window: ^0.1.6
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'dart:io';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
void main(List<String> args) {
debugPrint('args: $args');
if (runWebViewTitleBarWidget(args)) {
return;
}
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final TextEditingController _controller = TextEditingController(
text: 'https://example.com',
);
bool? _webviewAvailable;
@override
void initState() {
super.initState();
WebviewWindow.isWebviewAvailable().then((value) {
setState(() {
_webviewAvailable = value;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
actions: [
IconButton(
onPressed: () async {
final webview = await WebviewWindow.create(
configuration: CreateConfiguration(
windowHeight: 1280,
windowWidth: 720,
title: "ExampleTestWindow",
titleBarTopPadding: Platform.isMacOS ? 20 : 0,
userDataFolderWindows: await _getWebViewPath(),
),
);
webview
..registerJavaScriptMessageHandler("test", (name, body) {
debugPrint('on javaScipt message: $name $body');
})
..setApplicationNameForUserAgent(" WebviewExample/1.0.0")
..setPromptHandler((prompt, defaultText) {
if (prompt == "test") {
return "Hello World!";
} else if (prompt == "init") {
return "initial prompt";
}
return "";
})
..addScriptToExecuteOnDocumentCreated("""
const mixinContext = {
platform: 'Desktop',
conversation_id: 'conversationId',
immersive: false,
app_version: '1.0.0',
appearance: 'dark',
}
window.MixinContext = {
getContext: function() {
return JSON.stringify(mixinContext)
}
}
""")
..launch("http://localhost:3000/test.html");
},
icon: const Icon(Icons.bug_report),
)
],
),
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextField(controller: _controller),
const SizedBox(height: 16),
TextButton(
onPressed: _webviewAvailable != true ? null : _onTap,
child: const Text('Open'),
),
const SizedBox(height: 20),
TextButton(
onPressed: () async {
await WebviewWindow.clearAll(
userDataFolderWindows: await _getWebViewPath(),
);
debugPrint('clear complete');
},
child: const Text('Clear all'),
)
],
),
),
),
),
);
}
void _onTap() async {
final webview = await WebviewWindow.create(
configuration: CreateConfiguration(
userDataFolderWindows: await _getWebViewPath(),
titleBarTopPadding: Platform.isMacOS ? 20 : 0,
),
);
webview
..setBrightness(Brightness.dark)
..setApplicationNameForUserAgent("WebviewExample/1.0.0")
..launch(_controller.text)
..addOnUrlRequestCallback((url) {
debugPrint('url: $url');
final uri = Uri.parse(url);
if (uri.path == '/login_success') {
debugPrint('login success. token: ${uri.queryParameters['token']}');
webview.close();
}
})
..onClose.whenComplete(() {
debugPrint("on close");
});
await Future.delayed(const Duration(seconds: 2));
for (final javaScript in _javaScriptToEval) {
try {
final ret = await webview.evaluateJavaScript(javaScript);
debugPrint('evaluateJavaScript: $ret');
} catch (e) {
debugPrint('evaluateJavaScript error: $e \n $javaScript');
}
}
}
}
const _javaScriptToEval = [
"""
function test() {
return;
}
test();
""",
'eval({"name": "test", "user_agent": navigator.userAgent})',
'1 + 1',
'undefined',
'1.0 + 1.0',
'"test"',
];
Future<String> _getWebViewPath() async {
final document = await getApplicationDocumentsDirectory();
return p.join(
document.path,
'desktop_webview_window',
);
}
Download Details:
Author: MixinNetwork
Source Code: https://github.com/MixinNetwork/flutter-plugins/tree/main/packages/desktop_webview_window
1659718202
A flutter plugin which allows the usage of the android webkit API without needing to display anything.
<uses-permission android:name="android.permission.INTERNET" />
import 'package:flutter/material.dart';
import 'package:webview_without_view/webview_without_view.dart' as webview;
import 'home_page.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await webview.initialize();
runApp(HomePage());
}
After successfully initializing the webview_without_view plugin, you can load a page and retrieve the HTML source, if you wish to. (See the example app for more information)
When you are done with using the functions from webview_without_view, call dispose.
@override
void dispose() {
webview.dispose();
super.dispose();
}
Run this command:
With Flutter:
$ flutter pub add webview_without_view
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
webview_without_view: ^1.0.6
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:webview_without_view/webview_without_view.dart';
import 'package:flutter/material.dart';
import 'application.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Application.run();
}
Download Details:
Author: flutterplugins
Source Code: https://gitlab.com/flutterplugins/webview_without_view
1659312252
bootpay_webview_flutter 플러터 라이브러리
webview_flutter를 부트페이가 Fork 떠서 만든 웹뷰입니다. 이미 결제모듈이 동작하는 웹사이트에 webview로 링크만 연결하여 사용하실 웹앱 flutter 개발자분께서는 해당 모듈의 웹뷰를 사용하시면 쉽게 결제 진행이 가능하십니다.
pubspec.yaml
파일에 아래 모듈을 추가해주세요
...
dependencies:
...
bootpay_webview_flutter: last_version
...
따로 설정하실 것이 없습니다.
** {your project root}/ios/Runner/Info.plist ** CFBundleURLName
과 CFBundleURLSchemes
의 값은 개발사에서 고유값으로 지정해주셔야 합니다. 외부앱(카드사앱)에서 다시 기존 앱으로 앱투앱 호출시 필요한 스키마 값입니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>kr.co.bootpaySample</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bootpaySample</string>
</array>
</dict>
</array>
...
</dict>
</plist>
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:bootpay_webview_flutter/webview_flutter.dart';
void main() => runApp(MaterialApp(home: WebViewExample()));
class WebViewExample extends StatefulWidget {
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
@override
void initState() {
super.initState();
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@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.
),
// 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: Builder(builder: (BuildContext context) {
return WebView(
initialUrl: 'https://your.payweb.domain',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
onProgress: (int progress) {
print("WebView is loading (progress : $progress%)");
},
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('https://your.service.domain/')) {
print('allowing navigation to $request');
return NavigationDecision.navigate;
} else if(Platform.isAndroid) { //bootpay의 정상 수행을 위해 필요합니다
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');
},
);
}),
);
}
}
부트페이 개발매뉴얼을 참조해주세요
채팅으로 문의
Run this command:
With Flutter:
$ flutter pub add bootpay_webview_flutter
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
bootpay_webview_flutter: ^3.2.2
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:bootpay_webview_flutter/bootpay_webview_flutter.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:bootpay_webview_flutter/webview_flutter.dart';
void main() => runApp(MaterialApp(home: WebViewExample()));
class WebViewExample extends StatefulWidget {
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
@override
void initState() {
super.initState();
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@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.
),
// 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: Builder(builder: (BuildContext context) {
return WebView(
// initialUrl: 'https://your.payweb.domain',
initialUrl: 'https://d-cdn.bootapi.com/test/payment/',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
onProgress: (int progress) {
print("WebView is loading (progress : $progress%)");
},
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');
},
);
}),
);
}
}
Download Details:
Author: bootpay
Source Code: https://github.com/bootpay/bootpay_webview_flutter
1657470227
Add JsBridge Plugin to the WebView
A Flutter plugin that provides a JsBridge on WebView widget.
This plugin must introduce webview_flutter
Add flutter_jsbridge_plugin
as a dependency in your pubspec.yaml file.
dependencies:
flutter_jsbridge_plugin: ^0.0.4
...
final JsBridge _jsBridge = JsBridge();
...
WebView(
initialUrl: "https://www.baidu.com?timeStamp=${new DateTime.now().millisecondsSinceEpoch}",
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) async {
_jsBridge.loadJs(webViewController);
_controller.complete(webViewController);
_jsBridge.registerHandler("getToken", onCallBack: (data, func) {
// return token to js
func({"token": "token"});
});
_jsBridge.registerHandler("IAPpayment", onCallBack: (data, func) {
print("js call flutter iap");
});
_jsBridge.registerHandler("back", onCallBack: (data, func) {
print("js call flutter back");
});
},
navigationDelegate: (NavigationRequest request) {
if (_jsBridge.handlerUrl(request.url)) {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
onPageStarted: (url) {
_jsBridge.init();
},
))
Run this command:
With Flutter:
$ flutter pub add flutter_jsbridge_x
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
flutter_jsbridge_x: ^1.1.0
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:flutter_jsbridge_x/flutterjsbridgeplugin.dart';
import 'package:flutter_jsbridge_x/init_script.dart';
import 'package:flutter_jsbridge_x/js_bridge.dart';
import 'package:flutter_jsbridge_x/js_obj.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_jsbridge_x/flutterjsbridgeplugin.dart';
import 'package:flutter_jsbridge_x/js_bridge.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
final Completer<WebViewController> _controller =
Completer<WebViewController>();
final JsBridge _jsBridge = JsBridge();
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await Flutterjsbridgeplugin.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: WebView(
initialUrl: "https://www.baidu.com?timeStamp=${new DateTime.now().millisecondsSinceEpoch}",
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) async {
_jsBridge.loadJs(webViewController.evaluateJavascript);
_controller.complete(webViewController);
_jsBridge.registerHandler("getToken", onCallBack: (data, func) {
// return token to js
func({"token": "token"});
});
_jsBridge.registerHandler("IAPpayment", onCallBack: (data, func) {
print("js call flutter iap");
});
_jsBridge.registerHandler("back", onCallBack: (data, func) {
print("js call flutter back");
});
},
navigationDelegate: (NavigationRequest request) {
if (_jsBridge.handlerUrl(request.url)) {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
onPageStarted: (url) {
_jsBridge.init();
},
debuggingEnabled: true,
)),
);
}
}
Download Details:
Author: ibeilly
Source Code: https://github.com/ibeilly/flutter_jsbridge_plugin
1657467439
webview_flutter_web
This is an implementation of the webview_flutter
plugin for web.
It is currently severely limited and doesn't implement most of the available functionality. The following functionality is currently available:
loadUrl
(Without headers)requestUrl
loadHTMLString
(Without baseUrl
)initialUrl
through CreationParams
.Nothing else is currently supported.
This package is not an endorsed implementation of the webview_flutter
plugin yet, so it currently requires extra setup to use:
webview_flutter
.WebWebViewPlatform
as the WebView.platform
before creating a WebView
. See below for examples.Once those steps below are complete, the APIs from webview_flutter
listed above can be used as normal on web.
Before creating a WebView
(for instance, at the start of main
), you will need to register the web implementation.
...
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_web/webview_flutter_web.dart';
main() {
WebView.platform = WebWebViewPlatform();
...
If your project supports platforms other than web, you will need to use a conditional import to avoid directly including webview_flutter_web.dart
on non-web platforms. For example:
register_web_webview.dart
:
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_web/webview_flutter_web.dart';
void registerWebViewWebImplementation() {
WebView.platform = WebWebViewPlatform();
}
register_web_webview_stub.dart
:
void registerWebViewWebImplementation() {
// No-op.
}
main.dart
:
...
import 'register_web_webview_stub.dart'
if (dart.library.html) 'register_web.dart';
main() {
registerWebViewWebImplementation();
...
Run this command:
With Flutter:
$ flutter pub add webview_flutter_web
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
webview_flutter_web: ^0.1.0+4
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:webview_flutter_web/webview_flutter_web.dart';
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
import 'web_view.dart';
void main() {
runApp(const MaterialApp(home: _WebViewExample()));
}
class _WebViewExample extends StatefulWidget {
const _WebViewExample({Key? key}) : super(key: key);
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<_WebViewExample> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView example'),
actions: <Widget>[
_SampleMenu(_controller.future),
],
),
body: WebView(
initialUrl: 'https://flutter.dev',
onWebViewCreated: (WebViewController controller) {
_controller.complete(controller);
},
),
);
}
}
enum _MenuOptions {
doPostRequest,
}
class _SampleMenu extends StatelessWidget {
const _SampleMenu(this.controller);
final Future<WebViewController> controller;
@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.doPostRequest:
_onDoPostRequest(controller.data!, context);
break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.doPostRequest,
child: Text('Post Request'),
),
],
);
},
);
}
Future<void> _onDoPostRequest(
WebViewController controller, BuildContext context) async {
final WebViewRequest request = WebViewRequest(
uri: Uri.parse('https://httpbin.org/post'),
method: WebViewRequestMethod.post,
headers: <String, String>{'foo': 'bar', 'Content-Type': 'text/plain'},
body: Uint8List.fromList('Test Body'.codeUnits),
);
await controller.loadRequest(request);
}
}
Download Details:
Author:
1648728774
Desktop webview auth
This package enables Firebase OAuth on desktop via webview
The recaptcha verification flow is done on the local server, and it requires that the app has the following in the Release.entitlements
:
<key>com.apple.security.network.server</key>
<true/>
To display webview on Linux, libwebkit2gtk-4.0-dev
is used, if you don't have it already installed:
apt install libwebkit2gtk-4.0-dev
Additionally, if Flutter is installed using snap, you might face issues compiling the app, to fix you would need to uninstall the snap version and install Flutter manually on Linux.
Make sure you are on latest stable channle of Flutter, and have installed the requirements as mentioned here.
Nothing extra is needed to get started on Windows.
flutter pub add desktop_webview_auth
import 'package:desktop_webview_auth/desktop_webview_auth.dart';
import 'package:desktop_webview_auth/google.dart';
import 'package:desktop_webview_auth/facebook.dart';
import 'package:desktop_webview_auth/twitter.dart';
ProviderArgs
final googleSignInArgs = GoogleSignInArgs(
clientId:
'448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com',
redirectUri:
'https://react-native-firebase-testing.firebaseapp.com/__/auth/handler',
scope: 'email',
)
DesktopWebviewAuth.signIn
try {
final result = await DesktopWebviewAuth.signIn(args);
print(result?.accessToken);
print(result?.tokenSecret);
} catch (err) {
// something went wrong
}
OAuthCredential
and sign inimport 'package:firebase_auth/firebase_auth.dart';
final credential = GoogleAuthProvider.credential(accessToken: result.accessToken)
FirebaseAuth.instance.signInWithCredential(credential);
Run this command:
With Flutter:
$ flutter pub add desktop_webview_auth
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
desktop_webview_auth: ^0.0.6
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:desktop_webview_auth/desktop_webview_auth.dart';
// ignore_for_file: constant_identifier_names
import 'package:flutter/material.dart';
import 'package:googleapis/identitytoolkit/v3.dart';
import 'package:googleapis_auth/googleapis_auth.dart';
import 'package:desktop_webview_auth/desktop_webview_auth.dart';
import 'package:desktop_webview_auth/google.dart';
import 'package:desktop_webview_auth/facebook.dart';
import 'package:desktop_webview_auth/twitter.dart';
void main() {
runApp(const MyApp());
}
typedef SignInCallback = Future<void> Function();
const String apiKey = 'AIzaSyAgUhHU8wSJgO5MVNy95tMT07NEjzMOfz0';
const GOOGLE_CLIENT_ID =
'448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com';
const REDIRECT_URI =
'https://react-native-firebase-testing.firebaseapp.com/__/auth/handler';
const TWITTER_API_KEY = 'YEXSiWv5UeCHyy0c61O2LBC3B';
const TWITTER_API_SECRET_KEY =
'DOd9dCCRFgtnqMDQT7A68YuGZtvcO4WP1mEFS4mEJAUooM4yaE';
const FACEBOOK_CLIENT_ID = '128693022464535';
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
SignInCallback signInWithArgs(BuildContext context, ProviderArgs args) =>
() async {
final result = await DesktopWebviewAuth.signIn(args);
notify(context, result?.toString());
};
void notify(BuildContext context, String? result) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Result: $result'),
),
);
}
Future<void> getRecaptchaVerification(BuildContext context) async {
final client = clientViaApiKey(apiKey);
final identityToolkit = IdentityToolkitApi(client);
final res = identityToolkit.relyingparty;
final recaptchaResponse = await res.getRecaptchaParam();
final args = RecaptchaArgs(
siteKey: recaptchaResponse.recaptchaSiteKey!,
siteToken: recaptchaResponse.recaptchaStoken!,
);
final result = await DesktopWebviewAuth.recaptchaVerification(
args,
height: 600,
width: 600,
);
notify(context, result?.verificationId);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
padding: MaterialStateProperty.all(const EdgeInsets.all(20)),
),
),
),
home: Scaffold(
body: Builder(
builder: (context) {
final buttons = [
ElevatedButton(
child: const Text('Sign in with Google'),
onPressed: signInWithArgs(
context,
GoogleSignInArgs(
clientId: GOOGLE_CLIENT_ID,
redirectUri: REDIRECT_URI,
),
),
),
ElevatedButton(
child: const Text('Sign in with Twitter'),
onPressed: signInWithArgs(
context,
TwitterSignInArgs(
apiKey: TWITTER_API_KEY,
apiSecretKey: TWITTER_API_SECRET_KEY,
redirectUri: REDIRECT_URI,
),
),
),
ElevatedButton(
child: const Text('Sign in with Facebook'),
onPressed: signInWithArgs(
context,
FacebookSignInArgs(
clientId: FACEBOOK_CLIENT_ID,
redirectUri: REDIRECT_URI,
),
),
),
ElevatedButton(
child: const Text('Recaptcha Verification'),
onPressed: () => getRecaptchaVerification(context),
),
];
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 300),
child: ListView.separated(
itemCount: buttons.length,
shrinkWrap: true,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (context, index) {
return buttons[index];
},
),
),
);
},
),
),
);
}
}
Download Details:
Author: invertase
Source Code: https://github.com/invertase/flutter_desktop_webview_auth
1647358433
WebView for Flutter
A Flutter plugin that provides a WebView widget. Base on webview_flutter and enhance it. For the moment, webview_flutter and webview_flutter_max can not coexist in one project,otherwise it will cause duplicate class exception in ios.
Flutter webview插件 基于webview_flutter插件改造,并增强一些特性。 目前为止,不能在一个工程内同时引用webview_flutter和webview_flutter_max, 因为ios下会报重复类异常。
set http proxy flutter pub publish
Run this command:
With Flutter:
$ flutter pub add webview_flutter_max
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
webview_flutter_max: ^1.0.7
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:webview_flutter_max/platform_interface.dart';
import 'package:webview_flutter_max/webview_flutter.dart';
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'dart:convert';
import 'dart:io';
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>();
@override
void initState() {
super.initState();
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@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: Builder(builder: (BuildContext context) {
return WebView(
initialUrl: 'https://flutter.dev',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
onProgress: (int progress) {
print("WebView is loading (progress : $progress%)");
},
javascriptChannels: <JavascriptChannel>{
_toasterJavascriptChannel(context),
},
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');
},
gestureNavigationEnabled: true,
);
}),
floatingActionButton: favoriteButton(),
);
}
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toaster',
onMessageReceived: (JavascriptMessage message) {
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
});
}
Widget favoriteButton() {
return FutureBuilder<WebViewController>(
future: _controller.future,
builder: (BuildContext context,
AsyncSnapshot<WebViewController> controller) {
if (controller.hasData) {
return FloatingActionButton(
onPressed: () async {
final String url = (await controller.data!.currentUrl())!;
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('Favorited $url')),
);
},
child: const Icon(Icons.favorite),
);
}
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');
// ignore: deprecated_member_use
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";');
// ignore: deprecated_member_use
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();
// ignore: deprecated_member_use
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.';
}
// ignore: deprecated_member_use
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 {
// ignore: deprecated_member_use
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 {
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
const SnackBar(
content: Text("No forward history item")),
);
return;
}
},
),
IconButton(
icon: const Icon(Icons.replay),
onPressed: !webViewReady
? null
: () {
controller.reload();
},
),
],
);
},
);
}
}
Download Details:
Author:
Source Code: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter
1645336427
WebView for Flutter used x5 core
A Flutter plugin that provides a WebView widget.
On iOS the WebView widget is backed by a WKWebView; On Android the WebView widget is backed by a WebView.
Add webview_flutter
as a dependency in your pubspec.yaml file.
You can now include a WebView widget in your widget tree. See the WebView widget's Dartdoc for more details on how to use the widget.
The WebView is relying on Platform Views to embed the Android’s webview within the Flutter app. By default a Virtual Display based platform view backend is used, this implementation has multiple keyboard. When keyboard input is required we recommend using the Hybrid Composition based platform views implementation. Note that on Android versions prior to Android 10 Hybrid Composition has some performance drawbacks.
minSdkVersion
in android/app/build.gradle
:android {
defaultConfig {
minSdkVersion 19
}
}
This means that app will only be available for users that run Android SDK 19 or higher.
WebView.platform = SurfaceAndroidWebView();
in initState()
. For example:import 'dart:io';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewExample extends StatefulWidget {
@override
WebViewExampleState createState() => WebViewExampleState();
}
class WebViewExampleState extends State<WebViewExample> {
@override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@override
Widget build(BuildContext context) {
return WebView(
initialUrl: 'https://flutter.dev',
);
}
}
To use Material Components when the user interacts with input elements in the WebView, follow the steps described in the Enabling Material Components instructions.
Run this command:
With Flutter:
$ flutter pub add webview_flutter_x5
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
webview_flutter_x5: ^2.0.8+9
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:webview_flutter_x5/platform_interface.dart';
import 'package:webview_flutter_x5/webview_flutter.dart';
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:webview_flutter_x5/webview_flutter.dart';
import 'package:webview_flutter/webview_flutter.dart' as OriginWebview;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Webview demp',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({this.title = ''});
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
late FocusNode _focusNode;
ChooseFileMode chooseFileMode = ChooseFileMode.auto;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
// return Container();
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView(
children: <Widget>[
GestureDetector(
onTap: () {
WebviewFlutterX5.initX5(needPermissionCallback: (List<String> permissions) async {
List<Permission> permissionList = [];
for (final item in permissions) {
if (item == 'camera') {
permissionList.add(Permission.camera);
} else if (item == 'storage') {
permissionList.add(Permission.storage);
}
}
if (permissionList.isNotEmpty) {
Map<Permission, PermissionStatus> statuses = await permissionList.request();
print('dart层权限: $permissions $statuses');
}
});
},
child: Container(
width: 100.0,
height: 45.0,
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'X5初始化',
),
),
),
GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('X5内核加载状态'),
),
body: SafeArea(child: WebviewPage('http://soft.imtt.qq.com/browser/tes/feedback.html')),
);
}));
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'X5内核加载状态',
),
),
),
GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('x5内核加载测试'),
),
body: SafeArea(child: WebviewPage('http://debugtbs.qq.com/')),
);
}));
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'x5内核加载测试',
),
),
),
GestureDetector(
onTap: () async {
if (_focusNode.hasFocus) {
_focusNode.unfocus();
}
String url2 = 'https://www.baidu.com/';
await Navigator.push(context, MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('测试'),
),
body: SafeArea(child: WebviewPage(url2)),
);
}));
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'测试',
),
),
),
GestureDetector(
onTap: () async {
if (_focusNode.hasFocus) {
_focusNode.unfocus();
}
String url2 = 'https://www.baidu.com/';
await Navigator.push(context, MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('官方webview插件'),
),
body: SafeArea(
child: OriginWebview.WebView(
initialUrl: url2,
)),
);
}));
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'官方webview插件',
),
),
),
GestureDetector(
onTap: () async {
await WebviewFlutterX5.canGetDeviceId(false);
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'禁止获取imei',
),
),
),
GestureDetector(
onTap: () async {
await WebviewFlutterX5.canGetSubscriberId(false);
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'禁止获取IMSI',
),
),
),
GestureDetector(
onTap: () async {
await WebviewFlutterX5.canGetAndroidId(false);
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'禁止获取AndroidID',
),
),
),
GestureDetector(
onTap: () async {
await WebviewFlutterX5.manualPhoneModel();
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'设置手机型号',
),
),
),
GestureDetector(
onTap: () async {
await WebviewFlutterX5.manualPhoneSerial();
},
child: Container(
width: 100.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'设置SN',
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 200.0,
height: 45.0,
margin: EdgeInsets.only(top: 20),
color: Colors.blue[200],
alignment: Alignment.center,
child: Text(
'选择文件模式 $chooseFileMode',
),
),
PopupMenuButton<ChooseFileMode>(
icon: Icon(
Icons.add,
size: 24,
color: Colors.grey,
),
padding: EdgeInsets.zero,
onSelected: (value) async{
setState(() {
chooseFileMode = value;
});
await WebviewFlutterX5.setChooseFileMode(value);
},
itemBuilder: (BuildContext context) => getWidgets()),
],
),
],
),
),
);
}
List<PopupMenuEntry<ChooseFileMode>> getWidgets() {
List<ChooseFileMode> strategyList = ChooseFileMode.values;
List<PopupMenuEntry<ChooseFileMode>> widgets = [];
for (int i = 0; i < strategyList.length; i++) {
var ele = strategyList[i];
widgets.add(PopupMenuItem<ChooseFileMode>(
value: ele,
child: Container(
// color: Colors.green,
width: 180,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'$ele',
style: TextStyle(fontSize: 14.0),
),
if (ele == chooseFileMode)
Icon(
Icons.favorite,
size: 16.0,
color: Colors.blue,
)
else
Container(),
],
),
)));
widgets.add(const PopupMenuDivider());
}
return widgets;
}
}
class WebviewPage extends StatefulWidget {
WebviewPage(this.url);
final String url;
@override
_WebviewPageState createState() => _WebviewPageState();
}
class _WebviewPageState extends State<WebviewPage> {
final Completer<WebViewController> _completer = Completer<WebViewController>();
bool loading = true;
@override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebView(
initialUrl: widget.url,
onWebViewCreated: _completer.complete,
javascriptMode: JavascriptMode.unrestricted,
onPageStarted: (url) {
print('web view page test: start url:$url');
},
onPageFinished: (url) {
print('web view page test: finish url:$url');
setState(() {
loading = false;
});
},
navigationDelegate: (request) {
print('web view page test: url:${widget.url}');
// if (request.url.startsWith('http://')) {
// request.url.replaceFirst('http://', 'https://');
// }
print('web view page test: url:${widget.url}');
return NavigationDecision.navigate;
},
),
loading ? Center(child: CircularProgressIndicator()) : Container(),
],
);
}
}
Download Details:
Author: buaashuai
Source Code: https://github.com/buaashuai/plugins
1642836632
Tauri is a framework for building tiny, blazing fast binaries for all major desktop platforms. Developers can integrate any front-end framework that compiles to HTML, JS and CSS for building their user interface. The backend of the application is a rust-sourced binary with an API that the front-end can interact with.
The user interface in Tauri apps currently leverages tao
as a window handling library on macOS and Windows, and gtk
on Linux via the Tauri-team incubated and maintained WRY, which creates a unified interface to the system webview (and other goodies like Menu and Taskbar), leveraging WebKit on macOS, WebView2 on Windows and WebKitGTK on Linux.
To learn more about the details of how all of these pieces fit together, please consult this ARCHITECTURE.md document.
If you are interested in making a tauri-app, please visit the documentation website. This README is directed towards those who are interested in contributing to the core library. But if you just want a quick overview about where tauri
is at in its development, here's a quick burndown:
Detail | Tauri | Electron |
---|---|---|
Installer Size Linux | 3.1 MB | 52.1 MB |
Memory Consumption Linux | 180 MB | 462 MB |
Launch Time Linux | 0.39s | 0.80s |
Interface Service Provider | WRY | Chromium |
Backend Binding | Rust | Node.js (ECMAScript) |
Underlying Engine | Rust | V8 (C/C++) |
FLOSS | Yes | No |
Multithreading | Yes | Yes |
Bytecode Delivery | Yes | No |
Multiple Windows | Yes | Yes |
Auto Updater | Yes | Yes1 |
Custom App Icon | Yes | Yes |
Windows Binary | Yes | Yes |
MacOS Binary | Yes | Yes |
Linux Binary | Yes | Yes |
iOS Binary | Soon | No |
Android Binary | Soon | No |
Desktop Tray | Yes | Yes |
Sidecar Binaries | Yes | No |
Tauri is a system composed of a number of moving pieces:
Tauri core can be developed on Mac, Linux and Windows, but you are encouraged to use the latest possible operating systems and build tools for your OS.
Before you start working on something, it's best to check if there is an existing issue first. It's also is a good idea to stop by the Discord server and confirm with the team if it makes sense or if someone is already working on it.
Please make sure to read the Contributing Guide before making a pull request.
Thank you to everyone contributing to Tauri!
Documentation in a polyglot system is a tricky proposition. To this end, we prefer to use inline documentation of Rust code and at JSDoc in typescript / javascript code. We autocollect these and publish them using Docusaurus v2 and netlify. Here is the hosting repository for the documentation site: https://github.com/tauri-apps/tauri-docs
Test all the things! We have a number of test suites, but are always looking to improve our coverage:
cargo test
) => sourced via inline #[cfg(test)]
declarationsjest
) => via spec filesWe recommend you read this article to understand better how we run our pipelines: https://www.jacobbolda.com/setting-up-ci-and-cd-for-tauri/
Tauri aims to be a sustainable collective based on principles that guide sustainable free and open software communities. To this end it has become a Programme within the Commons Conservancy, and you can contribute financially via Open Collective.
Download Details:
Author: tauri-apps
Source Code: https://github.com/tauri-apps/tauri
#rust #webview #webdev #html #css #javascript
1642783035
flutter_macos_webview
Flutter plugin that lets you display native WebView on macOS
For docs see flutter_macos_webview.dart
Run this command:
With Flutter:
$ flutter pub add flutter_macos_webview
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
flutter_macos_webview: ^0.0.6
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:flutter_macos_webview/flutter_macos_webview.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_macos_webview/flutter_macos_webview.dart';
void main() => runApp(App());
class App extends StatelessWidget {
Future<void> _onOpenPressed(PresentationStyle presentationStyle) async {
final webview = FlutterMacOSWebView(
onOpen: () => print('Opened'),
onClose: () => print('Closed'),
onPageStarted: (url) => print('Page started: $url'),
onPageFinished: (url) => print('Page finished: $url'),
onWebResourceError: (err) {
print(
'Error: ${err.errorCode}, ${err.errorType}, ${err.domain}, ${err.description}',
);
},
);
await webview.open(
url: 'https://google.com',
presentationStyle: presentationStyle,
size: Size(400.0, 400.0),
userAgent:
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1',
);
// await Future.delayed(Duration(seconds: 5));
// await webview.close();
}
@override
Widget build(BuildContext context) {
return CupertinoApp(
debugShowCheckedModeBanner: false,
home: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CupertinoButton(
child: Text('Open as modal'),
onPressed: () => _onOpenPressed(PresentationStyle.modal),
),
SizedBox(height: 16.0),
CupertinoButton(
child: Text('Open as sheet'),
onPressed: () => _onOpenPressed(PresentationStyle.sheet),
),
],
),
);
}
}
Download Details:
Author: vanelizarov
Source Code: https://github.com/vanelizarov/flutter_macos_webview