Add All the files from the touchpad_demonstrator Flutter App which can be build for Android and iOS
@@ -0,0 +1,119 @@
|
||||
## 0.3.9
|
||||
|
||||
* Allow external packages to provide webview implementations for new platforms.
|
||||
|
||||
## 0.3.8+1
|
||||
|
||||
* Suppress deprecation warning for BinaryMessages. See: https://github.com/flutter/flutter/issues/33446
|
||||
|
||||
## 0.3.8
|
||||
|
||||
* Add `debuggingEnabled` property.
|
||||
|
||||
## 0.3.7+1
|
||||
|
||||
* Fix an issue where JavaScriptChannel messages weren't sent from the platform thread on Android.
|
||||
|
||||
## 0.3.7
|
||||
|
||||
* Fix loadUrlWithHeaders flaky test.
|
||||
|
||||
## 0.3.6+1
|
||||
|
||||
* Remove un-used method params in webview\_flutter
|
||||
|
||||
## 0.3.6
|
||||
|
||||
* Add an optional `headers` field to the controller.
|
||||
|
||||
## 0.3.5+5
|
||||
|
||||
* Fixed error in documentation of `javascriptChannels`.
|
||||
|
||||
## 0.3.5+4
|
||||
|
||||
* Fix bugs in the example app by updating it to use a `StatefulWidget`.
|
||||
|
||||
## 0.3.5+3
|
||||
|
||||
* Make sure to post javascript channel messages from the platform thread.
|
||||
|
||||
## 0.3.5+2
|
||||
|
||||
* Fix crash from `NavigationDelegate` on later versions of Android.
|
||||
|
||||
## 0.3.5+1
|
||||
|
||||
* Fix a bug where updates to onPageFinished were ignored.
|
||||
|
||||
## 0.3.5
|
||||
|
||||
* Added an onPageFinished callback.
|
||||
|
||||
## 0.3.4
|
||||
|
||||
* Support specifying navigation delegates that can prevent navigations from being executed.
|
||||
|
||||
## 0.3.3+2
|
||||
|
||||
* Exclude LongPress handler from semantics tree since it does nothing.
|
||||
|
||||
## 0.3.3+1
|
||||
|
||||
* Fixed a memory leak on Android - the WebView was not properly disposed.
|
||||
|
||||
## 0.3.3
|
||||
|
||||
* Add clearCache method to WebView controller.
|
||||
|
||||
## 0.3.2+1
|
||||
|
||||
* Log a more detailed warning at build time about the previous AndroidX
|
||||
migration.
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* Added CookieManager to interface with WebView cookies. Currently has the ability to clear cookies.
|
||||
|
||||
## 0.3.1
|
||||
|
||||
* Added JavaScript channels to facilitate message passing from JavaScript code running inside
|
||||
the WebView to the Flutter app's Dart code.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* **Breaking change**. Migrate from the deprecated original Android Support
|
||||
Library to AndroidX. This shouldn't result in any functional changes, but it
|
||||
requires any Android apps using this plugin to [also
|
||||
migrate](https://developer.android.com/jetpack/androidx/migrate) if they're
|
||||
using the original support library.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* Added a evaluateJavascript method to WebView controller.
|
||||
* (BREAKING CHANGE) Renamed the `JavaScriptMode` enum to `JavascriptMode`, and the WebView `javasScriptMode` parameter to `javascriptMode`.
|
||||
|
||||
## 0.1.2
|
||||
|
||||
* Added a reload method to the WebView controller.
|
||||
|
||||
## 0.1.1
|
||||
|
||||
* Added a `currentUrl` accessor for the WebView controller to look up what URL
|
||||
is being displayed.
|
||||
|
||||
## 0.1.0+1
|
||||
|
||||
* Fix null crash when initialUrl is unset on iOS.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* Add goBack, goForward, canGoBack, and canGoForward methods to the WebView controller.
|
||||
|
||||
## 0.0.1+1
|
||||
|
||||
* Fix case for "FLTWebViewFlutterPlugin" (iOS was failing to buld on case-sensitive file systems).
|
||||
|
||||
## 0.0.1
|
||||
|
||||
* Initial release.
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,31 @@
|
||||
# WebView for Flutter (Developers Preview)
|
||||
|
||||
[](https://pub.dartlang.org/packages/webview_flutter)
|
||||
|
||||
A Flutter plugin that provides a WebView widget.
|
||||
|
||||
On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview);
|
||||
On Android the WebView widget is backed by a [WebView](https://developer.android.com/reference/android/webkit/WebView).
|
||||
|
||||
## Developers Preview Status
|
||||
The plugin relies on Flutter's new mechanism for embedding Android and iOS views.
|
||||
As that mechanism is currently in a developers preview, this plugin should also be
|
||||
considered a developers preview.
|
||||
|
||||
Known issues are tagged with the [platform-views](https://github.com/flutter/flutter/labels/a%3A%20platform-views) and/or [webview](https://github.com/flutter/flutter/labels/p%3A%20webview) labels.
|
||||
|
||||
To use this plugin on iOS you need to opt-in for the embedded views preview by
|
||||
adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview`
|
||||
and the value `YES`.
|
||||
|
||||
## Setup
|
||||
|
||||
### iOS
|
||||
Opt-in to the embedded views preview by adding a boolean property to the app's `Info.plist` file
|
||||
with the key `io.flutter.embedded_views_preview` and the value `YES`.
|
||||
|
||||
## Usage
|
||||
Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,51 @@
|
||||
def PLUGIN = "webview_flutter";
|
||||
def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning";
|
||||
gradle.buildFinished { buildResult ->
|
||||
if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) {
|
||||
println ' *********************************************************'
|
||||
println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.'
|
||||
println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.'
|
||||
println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.'
|
||||
println ' *********************************************************'
|
||||
rootProject.ext.set(ANDROIDX_WARNING, true);
|
||||
}
|
||||
}
|
||||
|
||||
group 'io.flutter.plugins.webviewflutter'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.webkit:webkit:1.0.0'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'webview_flutter'
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest package="io.flutter.plugins.webviewflutter">
|
||||
</manifest>
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.webviewflutter;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.ValueCallback;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
|
||||
class FlutterCookieManager implements MethodCallHandler {
|
||||
|
||||
private FlutterCookieManager() {
|
||||
// Do not instantiate.
|
||||
// This class should only be used in context of a BinaryMessenger.
|
||||
// Use FlutterCookieManager#registerWith instead.
|
||||
}
|
||||
|
||||
static void registerWith(BinaryMessenger messenger) {
|
||||
MethodChannel methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager");
|
||||
FlutterCookieManager cookieManager = new FlutterCookieManager();
|
||||
methodChannel.setMethodCallHandler(cookieManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(MethodCall methodCall, Result result) {
|
||||
switch (methodCall.method) {
|
||||
case "clearCookies":
|
||||
clearCookies(result);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearCookies(final Result result) {
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
final boolean hasCookies = cookieManager.hasCookies();
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.removeAllCookies(
|
||||
new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
public void onReceiveValue(Boolean value) {
|
||||
result.success(hasCookies);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cookieManager.removeAllCookie();
|
||||
result.success(hasCookies);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.webviewflutter;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.webkit.WebStorage;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
import io.flutter.plugin.platform.PlatformView;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FlutterWebView implements PlatformView, MethodCallHandler {
|
||||
private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames";
|
||||
private final WebView webView;
|
||||
private final MethodChannel methodChannel;
|
||||
private final FlutterWebViewClient flutterWebViewClient;
|
||||
private final Handler platformThreadHandler;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
FlutterWebView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) {
|
||||
webView = new WebView(context);
|
||||
platformThreadHandler = new Handler(context.getMainLooper());
|
||||
// Allow local storage.
|
||||
webView.getSettings().setDomStorageEnabled(true);
|
||||
|
||||
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
|
||||
methodChannel.setMethodCallHandler(this);
|
||||
|
||||
flutterWebViewClient = new FlutterWebViewClient(methodChannel);
|
||||
applySettings((Map<String, Object>) params.get("settings"));
|
||||
|
||||
if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) {
|
||||
registerJavaScriptChannelNames((List<String>) params.get(JS_CHANNEL_NAMES_FIELD));
|
||||
}
|
||||
|
||||
if (params.containsKey("initialUrl")) {
|
||||
String url = (String) params.get("initialUrl");
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return webView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(MethodCall methodCall, Result result) {
|
||||
switch (methodCall.method) {
|
||||
case "loadUrl":
|
||||
loadUrl(methodCall, result);
|
||||
break;
|
||||
case "updateSettings":
|
||||
updateSettings(methodCall, result);
|
||||
break;
|
||||
case "canGoBack":
|
||||
canGoBack(result);
|
||||
break;
|
||||
case "canGoForward":
|
||||
canGoForward(result);
|
||||
break;
|
||||
case "goBack":
|
||||
goBack(result);
|
||||
break;
|
||||
case "goForward":
|
||||
goForward(result);
|
||||
break;
|
||||
case "reload":
|
||||
reload(result);
|
||||
break;
|
||||
case "currentUrl":
|
||||
currentUrl(result);
|
||||
break;
|
||||
case "evaluateJavascript":
|
||||
evaluateJavaScript(methodCall, result);
|
||||
break;
|
||||
case "addJavascriptChannels":
|
||||
addJavaScriptChannels(methodCall, result);
|
||||
break;
|
||||
case "removeJavascriptChannels":
|
||||
removeJavaScriptChannels(methodCall, result);
|
||||
break;
|
||||
case "clearCache":
|
||||
clearCache(result);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void loadUrl(MethodCall methodCall, Result result) {
|
||||
Map<String, Object> request = (Map<String, Object>) methodCall.arguments;
|
||||
String url = (String) request.get("url");
|
||||
Map<String, String> headers = (Map<String, String>) request.get("headers");
|
||||
if (headers == null) {
|
||||
headers = Collections.emptyMap();
|
||||
}
|
||||
webView.loadUrl(url, headers);
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
private void canGoBack(Result result) {
|
||||
result.success(webView.canGoBack());
|
||||
}
|
||||
|
||||
private void canGoForward(Result result) {
|
||||
result.success(webView.canGoForward());
|
||||
}
|
||||
|
||||
private void goBack(Result result) {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
}
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
private void goForward(Result result) {
|
||||
if (webView.canGoForward()) {
|
||||
webView.goForward();
|
||||
}
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
private void reload(Result result) {
|
||||
webView.reload();
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
private void currentUrl(Result result) {
|
||||
result.success(webView.getUrl());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void updateSettings(MethodCall methodCall, Result result) {
|
||||
applySettings((Map<String, Object>) methodCall.arguments);
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
private void evaluateJavaScript(MethodCall methodCall, final Result result) {
|
||||
String jsString = (String) methodCall.arguments;
|
||||
if (jsString == null) {
|
||||
throw new UnsupportedOperationException("JavaScript string cannot be null");
|
||||
}
|
||||
webView.evaluateJavascript(
|
||||
jsString,
|
||||
new android.webkit.ValueCallback<String>() {
|
||||
@Override
|
||||
public void onReceiveValue(String value) {
|
||||
result.success(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addJavaScriptChannels(MethodCall methodCall, Result result) {
|
||||
List<String> channelNames = (List<String>) methodCall.arguments;
|
||||
registerJavaScriptChannelNames(channelNames);
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void removeJavaScriptChannels(MethodCall methodCall, Result result) {
|
||||
List<String> channelNames = (List<String>) methodCall.arguments;
|
||||
for (String channelName : channelNames) {
|
||||
webView.removeJavascriptInterface(channelName);
|
||||
}
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
private void clearCache(Result result) {
|
||||
webView.clearCache(true);
|
||||
WebStorage.getInstance().deleteAllData();
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
private void applySettings(Map<String, Object> settings) {
|
||||
for (String key : settings.keySet()) {
|
||||
switch (key) {
|
||||
case "jsMode":
|
||||
updateJsMode((Integer) settings.get(key));
|
||||
break;
|
||||
case "hasNavigationDelegate":
|
||||
final boolean hasNavigationDelegate = (boolean) settings.get(key);
|
||||
|
||||
final WebViewClient webViewClient =
|
||||
flutterWebViewClient.createWebViewClient(hasNavigationDelegate);
|
||||
|
||||
webView.setWebViewClient(webViewClient);
|
||||
break;
|
||||
case "debuggingEnabled":
|
||||
final boolean debuggingEnabled = (boolean) settings.get(key);
|
||||
|
||||
webView.setWebContentsDebuggingEnabled(debuggingEnabled);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown WebView setting: " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateJsMode(int mode) {
|
||||
switch (mode) {
|
||||
case 0: // disabled
|
||||
webView.getSettings().setJavaScriptEnabled(false);
|
||||
break;
|
||||
case 1: // unrestricted
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Trying to set unknown JavaScript mode: " + mode);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerJavaScriptChannelNames(List<String> channelNames) {
|
||||
for (String channelName : channelNames) {
|
||||
webView.addJavascriptInterface(
|
||||
new JavaScriptChannel(methodChannel, channelName, platformThreadHandler), channelName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
methodChannel.setMethodCallHandler(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.webviewflutter;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.webkit.WebViewClientCompat;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
// We need to use WebViewClientCompat to get
|
||||
// shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
|
||||
// invoked by the webview on older Android devices, without it pages that use iframes will
|
||||
// be broken when a navigationDelegate is set on Android version earlier than N.
|
||||
class FlutterWebViewClient {
|
||||
private static final String TAG = "FlutterWebViewClient";
|
||||
private final MethodChannel methodChannel;
|
||||
private boolean hasNavigationDelegate;
|
||||
|
||||
FlutterWebViewClient(MethodChannel methodChannel) {
|
||||
this.methodChannel = methodChannel;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
if (!hasNavigationDelegate) {
|
||||
return false;
|
||||
}
|
||||
notifyOnNavigationRequest(
|
||||
request.getUrl().toString(), request.getRequestHeaders(), view, request.isForMainFrame());
|
||||
// We must make a synchronous decision here whether to allow the navigation or not,
|
||||
// if the Dart code has set a navigation delegate we want that delegate to decide whether
|
||||
// to navigate or not, and as we cannot get a response from the Dart delegate synchronously we
|
||||
// return true here to block the navigation, if the Dart delegate decides to allow the
|
||||
// navigation the plugin will later make an addition loadUrl call for this url.
|
||||
//
|
||||
// Since we cannot call loadUrl for a subframe, we currently only allow the delegate to stop
|
||||
// navigations that target the main frame, if the request is not for the main frame
|
||||
// we just return false to allow the navigation.
|
||||
//
|
||||
// For more details see: https://github.com/flutter/flutter/issues/25329#issuecomment-464863209
|
||||
return request.isForMainFrame();
|
||||
}
|
||||
|
||||
private boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (!hasNavigationDelegate) {
|
||||
return false;
|
||||
}
|
||||
// This version of shouldOverrideUrlLoading is only invoked by the webview on devices with
|
||||
// webview versions earlier than 67(it is also invoked when hasNavigationDelegate is false).
|
||||
// On these devices we cannot tell whether the navigation is targeted to the main frame or not.
|
||||
// We proceed assuming that the navigation is targeted to the main frame. If the page had any
|
||||
// frames they will be loaded in the main frame instead.
|
||||
Log.w(
|
||||
TAG,
|
||||
"Using a navigationDelegate with an old webview implementation, pages with frames or iframes will not work");
|
||||
notifyOnNavigationRequest(url, null, view, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPageFinished(WebView view, String url) {
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
args.put("url", url);
|
||||
methodChannel.invokeMethod("onPageFinished", args);
|
||||
}
|
||||
|
||||
private void notifyOnNavigationRequest(
|
||||
String url, Map<String, String> headers, WebView webview, boolean isMainFrame) {
|
||||
HashMap<String, Object> args = new HashMap<>();
|
||||
args.put("url", url);
|
||||
args.put("isForMainFrame", isMainFrame);
|
||||
if (isMainFrame) {
|
||||
methodChannel.invokeMethod(
|
||||
"navigationRequest", args, new OnNavigationRequestResult(url, headers, webview));
|
||||
} else {
|
||||
methodChannel.invokeMethod("navigationRequest", args);
|
||||
}
|
||||
}
|
||||
|
||||
// This method attempts to avoid using WebViewClientCompat due to bug
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see
|
||||
// https://github.com/flutter/flutter/issues/29446.
|
||||
WebViewClient createWebViewClient(boolean hasNavigationDelegate) {
|
||||
this.hasNavigationDelegate = hasNavigationDelegate;
|
||||
|
||||
if (!hasNavigationDelegate || android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return internalCreateWebViewClient();
|
||||
}
|
||||
|
||||
return internalCreateWebViewClientCompat();
|
||||
}
|
||||
|
||||
private WebViewClient internalCreateWebViewClient() {
|
||||
return new WebViewClient() {
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
FlutterWebViewClient.this.onPageFinished(view, url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private WebViewClientCompat internalCreateWebViewClientCompat() {
|
||||
return new WebViewClientCompat() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(
|
||||
@NonNull WebView view, @NonNull WebResourceRequest request) {
|
||||
return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
FlutterWebViewClient.this.onPageFinished(view, url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class OnNavigationRequestResult implements MethodChannel.Result {
|
||||
private final String url;
|
||||
private final Map<String, String> headers;
|
||||
private final WebView webView;
|
||||
|
||||
private OnNavigationRequestResult(String url, Map<String, String> headers, WebView webView) {
|
||||
this.url = url;
|
||||
this.headers = headers;
|
||||
this.webView = webView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Object shouldLoad) {
|
||||
Boolean typedShouldLoad = (Boolean) shouldLoad;
|
||||
if (typedShouldLoad) {
|
||||
loadUrl();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String errorCode, String s1, Object o) {
|
||||
throw new IllegalStateException("navigationRequest calls must succeed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notImplemented() {
|
||||
throw new IllegalStateException(
|
||||
"navigationRequest must be implemented by the webview method channel");
|
||||
}
|
||||
|
||||
private void loadUrl() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
webView.loadUrl(url, headers);
|
||||
} else {
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.webviewflutter;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets
|
||||
* up.
|
||||
*
|
||||
* <p>Exposes a single method named `postMessage` to JavaScript, which sends a message over a method
|
||||
* channel to the Dart code.
|
||||
*/
|
||||
class JavaScriptChannel {
|
||||
private final MethodChannel methodChannel;
|
||||
private final String javaScriptChannelName;
|
||||
private final Handler platformThreadHandler;
|
||||
|
||||
/**
|
||||
* @param methodChannel the Flutter WebView method channel to which JS messages are sent
|
||||
* @param javaScriptChannelName the name of the JavaScript channel, this is sent over the method
|
||||
* channel with each message to let the Dart code know which JavaScript channel the message
|
||||
* was sent through
|
||||
*/
|
||||
JavaScriptChannel(
|
||||
MethodChannel methodChannel, String javaScriptChannelName, Handler platformThreadHandler) {
|
||||
this.methodChannel = methodChannel;
|
||||
this.javaScriptChannelName = javaScriptChannelName;
|
||||
this.platformThreadHandler = platformThreadHandler;
|
||||
}
|
||||
|
||||
// Suppressing unused warning as this is invoked from JavaScript.
|
||||
@SuppressWarnings("unused")
|
||||
@JavascriptInterface
|
||||
public void postMessage(final String message) {
|
||||
Runnable postMessageRunnable =
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
HashMap<String, String> arguments = new HashMap<>();
|
||||
arguments.put("channel", javaScriptChannelName);
|
||||
arguments.put("message", message);
|
||||
methodChannel.invokeMethod("javascriptChannelMessage", arguments);
|
||||
}
|
||||
};
|
||||
if (platformThreadHandler.getLooper() == Looper.myLooper()) {
|
||||
postMessageRunnable.run();
|
||||
} else {
|
||||
platformThreadHandler.post(postMessageRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.webviewflutter;
|
||||
|
||||
import android.content.Context;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.StandardMessageCodec;
|
||||
import io.flutter.plugin.platform.PlatformView;
|
||||
import io.flutter.plugin.platform.PlatformViewFactory;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebViewFactory extends PlatformViewFactory {
|
||||
private final BinaryMessenger messenger;
|
||||
|
||||
public WebViewFactory(BinaryMessenger messenger) {
|
||||
super(StandardMessageCodec.INSTANCE);
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PlatformView create(Context context, int id, Object args) {
|
||||
Map<String, Object> params = (Map<String, Object>) args;
|
||||
return new FlutterWebView(context, messenger, id, params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.webviewflutter;
|
||||
|
||||
import io.flutter.plugin.common.PluginRegistry.Registrar;
|
||||
|
||||
/** WebViewFlutterPlugin */
|
||||
public class WebViewFlutterPlugin {
|
||||
|
||||
/** Plugin registration. */
|
||||
public static void registerWith(Registrar registrar) {
|
||||
registrar
|
||||
.platformViewRegistry()
|
||||
.registerViewFactory(
|
||||
"plugins.flutter.io/webview", new WebViewFactory(registrar.messenger()));
|
||||
FlutterCookieManager.registerWith(registrar.messenger());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# webview_flutter_example
|
||||
|
||||
Demonstrates how to use the webview_flutter plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
For help getting started with Flutter, view our online
|
||||
[documentation](https://flutter.io/).
|
||||
@@ -0,0 +1,61 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "io.flutter.plugins.webviewflutterexample"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,39 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.flutter.plugins.webviewflutterexample">
|
||||
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="webview_flutter_example"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- This keeps the window background of the activity showing
|
||||
until Flutter renders its first frame. It can be removed if
|
||||
there is no splash screen (such as the default splash screen
|
||||
defined in @style/LaunchTheme). -->
|
||||
<meta-data
|
||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
||||
android:value="true" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.webviewflutterexample;
|
||||
|
||||
import android.os.Bundle;
|
||||
import io.flutter.app.FlutterActivity;
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
GeneratedPluginRegistrant.registerWith(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,29 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
@@ -0,0 +1,6 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
|
||||
@@ -0,0 +1,15 @@
|
||||
include ':app'
|
||||
|
||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
||||
|
||||
def plugins = new Properties()
|
||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
||||
if (pluginsFile.exists()) {
|
||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
|
||||
}
|
||||
|
||||
plugins.each { name, path ->
|
||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
||||
include ":$name"
|
||||
project(":$name").projectDir = pluginDirectory
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>8.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,489 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
|
||||
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
|
||||
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
|
||||
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
|
||||
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
9D26F6F82D91F92CC095EBA9 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
|
||||
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
|
||||
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
|
||||
9D26F6F82D91F92CC095EBA9 /* libPods-Runner.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B80C3931E831B6300D905FE /* App.framework */,
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEBA1CF902C7004384FC /* Flutter.framework */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
C6FFB52F5C2B8A41A7E39DE2 /* Pods */,
|
||||
B6736FC417BDCCDA377E779D /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
|
||||
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
97C146F11CF9000F007C117D /* Supporting Files */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F11CF9000F007C117D /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146F21CF9000F007C117D /* main.m */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B6736FC417BDCCDA377E779D /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8B845D8FBDE0AAD6BE1A0386 /* libPods-Runner.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C6FFB52F5C2B8A41A7E39DE2 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
B71376B4FB8384EF9D5F3F84 /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
A1F14D6FD37A3C5047F5A5AD /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0910;
|
||||
ORGANIZATIONNAME = "The Chromium Authors";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
A1F14D6FD37A3C5047F5A5AD /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
|
||||
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B71376B4FB8384EF9D5F3F84 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
|
||||
97C146F31CF9000F007C117D /* main.m in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.webviewFlutterExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.webviewFlutterExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0910"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>BuildSystemType</key>
|
||||
<string>Original</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : FlutterAppDelegate
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "AppDelegate.h"
|
||||
#include "GeneratedPluginRegistrant.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
[GeneratedPluginRegistrant registerWithRegistry:self];
|
||||
// Override point for customization after application launch.
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 564 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>webview_flutter_example</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>io.flutter.embedded_views_preview</key>
|
||||
<true/>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
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>();
|
||||
|
||||
@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);
|
||||
},
|
||||
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
|
||||
// ignore: prefer_collection_literals
|
||||
javascriptChannels: <JavascriptChannel>[
|
||||
_toasterJavascriptChannel(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;
|
||||
},
|
||||
onPageFinished: (String url) {
|
||||
print('Page finished loading: $url');
|
||||
},
|
||||
);
|
||||
}),
|
||||
floatingActionButton: favoriteButton(),
|
||||
);
|
||||
}
|
||||
|
||||
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
|
||||
return JavascriptChannel(
|
||||
name: 'Toaster',
|
||||
onMessageReceived: (JavascriptMessage message) {
|
||||
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();
|
||||
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.
|
||||
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));
|
||||
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()) {
|
||||
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()) {
|
||||
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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
name: webview_flutter_example
|
||||
description: Demonstrates how to use the webview_flutter plugin.
|
||||
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
webview_flutter:
|
||||
path: ../
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2019, the Chromium project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
void main() {
|
||||
final Completer<String> allTestsCompleter = Completer<String>();
|
||||
enableFlutterDriverExtension(handler: (_) => allTestsCompleter.future);
|
||||
tearDownAll(() => allTestsCompleter.complete(null));
|
||||
|
||||
test('initalUrl', () async {
|
||||
final Completer<WebViewController> controllerCompleter =
|
||||
Completer<WebViewController>();
|
||||
await pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WebView(
|
||||
key: GlobalKey(),
|
||||
initialUrl: 'https://flutter.dev/',
|
||||
onWebViewCreated: (WebViewController controller) {
|
||||
controllerCompleter.complete(controller);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
final WebViewController controller = await controllerCompleter.future;
|
||||
final String currentUrl = await controller.currentUrl();
|
||||
expect(currentUrl, 'https://flutter.dev/');
|
||||
});
|
||||
|
||||
test('loadUrl', () async {
|
||||
final Completer<WebViewController> controllerCompleter =
|
||||
Completer<WebViewController>();
|
||||
await pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WebView(
|
||||
key: GlobalKey(),
|
||||
initialUrl: 'https://flutter.dev/',
|
||||
onWebViewCreated: (WebViewController controller) {
|
||||
controllerCompleter.complete(controller);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
final WebViewController controller = await controllerCompleter.future;
|
||||
await controller.loadUrl('https://www.google.com/');
|
||||
final String currentUrl = await controller.currentUrl();
|
||||
expect(currentUrl, 'https://www.google.com/');
|
||||
});
|
||||
|
||||
// enable this once https://github.com/flutter/flutter/issues/31510
|
||||
// is resolved.
|
||||
test('loadUrl with headers', () async {
|
||||
final Completer<WebViewController> controllerCompleter =
|
||||
Completer<WebViewController>();
|
||||
final StreamController<String> pageLoads = StreamController<String>();
|
||||
await pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WebView(
|
||||
key: GlobalKey(),
|
||||
initialUrl: 'https://flutter.dev/',
|
||||
onWebViewCreated: (WebViewController controller) {
|
||||
controllerCompleter.complete(controller);
|
||||
},
|
||||
javascriptMode: JavascriptMode.unrestricted,
|
||||
onPageFinished: (String url) {
|
||||
pageLoads.add(url);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
final WebViewController controller = await controllerCompleter.future;
|
||||
final Map<String, String> headers = <String, String>{
|
||||
'test_header': 'flutter_test_header'
|
||||
};
|
||||
await controller.loadUrl('https://flutter-header-echo.herokuapp.com/',
|
||||
headers: headers);
|
||||
final String currentUrl = await controller.currentUrl();
|
||||
expect(currentUrl, 'https://flutter-header-echo.herokuapp.com/');
|
||||
|
||||
await pageLoads.stream.firstWhere((String url) => url == currentUrl);
|
||||
final String content = await controller
|
||||
.evaluateJavascript('document.documentElement.innerText');
|
||||
expect(content.contains('flutter_test_header'), isTrue);
|
||||
});
|
||||
|
||||
test('JavaScriptChannel', () async {
|
||||
final Completer<WebViewController> controllerCompleter =
|
||||
Completer<WebViewController>();
|
||||
final Completer<void> pageLoaded = Completer<void>();
|
||||
final List<String> messagesReceived = <String>[];
|
||||
await pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WebView(
|
||||
key: GlobalKey(),
|
||||
// This is the data URL for: '<!DOCTYPE html>'
|
||||
initialUrl:
|
||||
'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+',
|
||||
onWebViewCreated: (WebViewController controller) {
|
||||
controllerCompleter.complete(controller);
|
||||
},
|
||||
javascriptMode: JavascriptMode.unrestricted,
|
||||
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
|
||||
// ignore: prefer_collection_literals
|
||||
javascriptChannels: <JavascriptChannel>[
|
||||
JavascriptChannel(
|
||||
name: 'Echo',
|
||||
onMessageReceived: (JavascriptMessage message) {
|
||||
messagesReceived.add(message.message);
|
||||
},
|
||||
),
|
||||
].toSet(),
|
||||
onPageFinished: (String url) {
|
||||
pageLoaded.complete(null);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
final WebViewController controller = await controllerCompleter.future;
|
||||
await pageLoaded.future;
|
||||
|
||||
expect(messagesReceived, isEmpty);
|
||||
await controller.evaluateJavascript('Echo.postMessage("hello");');
|
||||
expect(messagesReceived, equals(<String>['hello']));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> pumpWidget(Widget widget) {
|
||||
runApp(widget);
|
||||
return WidgetsBinding.instance.endOfFrame;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2019, the Chromium project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
final FlutterDriver driver = await FlutterDriver.connect();
|
||||
await driver.requestData(null, timeout: const Duration(minutes: 1));
|
||||
driver.close();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#import <Flutter/Flutter.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLTCookieManager : NSObject <FlutterPlugin>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,49 @@
|
||||
#import "FLTCookieManager.h"
|
||||
|
||||
@implementation FLTCookieManager {
|
||||
}
|
||||
|
||||
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
|
||||
FLTCookieManager *instance = [[FLTCookieManager alloc] init];
|
||||
|
||||
FlutterMethodChannel *channel =
|
||||
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/cookie_manager"
|
||||
binaryMessenger:[registrar messenger]];
|
||||
[registrar addMethodCallDelegate:instance channel:channel];
|
||||
}
|
||||
|
||||
- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
|
||||
if ([[call method] isEqualToString:@"clearCookies"]) {
|
||||
[self clearCookies:result];
|
||||
} else {
|
||||
result(FlutterMethodNotImplemented);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearCookies:(FlutterResult)result {
|
||||
if (@available(iOS 9.0, *)) {
|
||||
[self clearCookiesIos9AndLater:result];
|
||||
} else {
|
||||
// support for iOS8 tracked in https://github.com/flutter/flutter/issues/27624.
|
||||
NSLog(@"Clearing cookies is not supported for Flutter WebViews prior to iOS 9.");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearCookiesIos9AndLater:(FlutterResult)result {
|
||||
NSSet *websiteDataTypes = [NSSet setWithArray:@[ WKWebsiteDataTypeCookies ]];
|
||||
WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
|
||||
|
||||
void (^deleteAndNotify)(NSArray<WKWebsiteDataRecord *> *) =
|
||||
^(NSArray<WKWebsiteDataRecord *> *cookies) {
|
||||
BOOL hasCookies = cookies.count > 0;
|
||||
[dataStore removeDataOfTypes:websiteDataTypes
|
||||
forDataRecords:cookies
|
||||
completionHandler:^{
|
||||
result(@(hasCookies));
|
||||
}];
|
||||
};
|
||||
|
||||
[dataStore fetchDataRecordsOfTypes:websiteDataTypes completionHandler:deleteAndNotify];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLTWKNavigationDelegate : NSObject <WKNavigationDelegate>
|
||||
|
||||
- (instancetype)initWithChannel:(FlutterMethodChannel*)channel;
|
||||
|
||||
/**
|
||||
* Whether to delegate navigation decisions over the method channel.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL hasDartNavigationDelegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "FLTWKNavigationDelegate.h"
|
||||
|
||||
@implementation FLTWKNavigationDelegate {
|
||||
FlutterMethodChannel* _methodChannel;
|
||||
}
|
||||
|
||||
- (instancetype)initWithChannel:(FlutterMethodChannel*)channel {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_methodChannel = channel;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView*)webView
|
||||
decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction
|
||||
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
|
||||
if (!self.hasDartNavigationDelegate) {
|
||||
decisionHandler(WKNavigationActionPolicyAllow);
|
||||
return;
|
||||
}
|
||||
NSDictionary* arguments = @{
|
||||
@"url" : navigationAction.request.URL.absoluteString,
|
||||
@"isForMainFrame" : @(navigationAction.targetFrame.isMainFrame)
|
||||
};
|
||||
[_methodChannel invokeMethod:@"navigationRequest"
|
||||
arguments:arguments
|
||||
result:^(id _Nullable result) {
|
||||
if ([result isKindOfClass:[FlutterError class]]) {
|
||||
NSLog(@"navigationRequest has unexpectedly completed with an error, "
|
||||
@"allowing navigation.");
|
||||
decisionHandler(WKNavigationActionPolicyAllow);
|
||||
return;
|
||||
}
|
||||
if (result == FlutterMethodNotImplemented) {
|
||||
NSLog(@"navigationRequest was unexepectedly not implemented: %@, "
|
||||
@"allowing navigation.",
|
||||
result);
|
||||
decisionHandler(WKNavigationActionPolicyAllow);
|
||||
return;
|
||||
}
|
||||
if (![result isKindOfClass:[NSNumber class]]) {
|
||||
NSLog(@"navigationRequest unexpectedly returned a non boolean value: "
|
||||
@"%@, allowing navigation.",
|
||||
result);
|
||||
decisionHandler(WKNavigationActionPolicyAllow);
|
||||
return;
|
||||
}
|
||||
NSNumber* typedResult = result;
|
||||
decisionHandler([typedResult boolValue] ? WKNavigationActionPolicyAllow
|
||||
: WKNavigationActionPolicyCancel);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation {
|
||||
[_methodChannel invokeMethod:@"onPageFinished" arguments:@{@"url" : webView.URL.absoluteString}];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLTWebViewController : NSObject <FlutterPlatformView>
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
viewIdentifier:(int64_t)viewId
|
||||
arguments:(id _Nullable)args
|
||||
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
|
||||
|
||||
- (UIView*)view;
|
||||
@end
|
||||
|
||||
@interface FLTWebViewFactory : NSObject <FlutterPlatformViewFactory>
|
||||
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,309 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "FlutterWebView.h"
|
||||
#import "FLTWKNavigationDelegate.h"
|
||||
#import "JavaScriptChannelHandler.h"
|
||||
|
||||
@implementation FLTWebViewFactory {
|
||||
NSObject<FlutterBinaryMessenger>* _messenger;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_messenger = messenger;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
|
||||
return [FlutterStandardMessageCodec sharedInstance];
|
||||
}
|
||||
|
||||
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
|
||||
viewIdentifier:(int64_t)viewId
|
||||
arguments:(id _Nullable)args {
|
||||
FLTWebViewController* webviewController = [[FLTWebViewController alloc] initWithFrame:frame
|
||||
viewIdentifier:viewId
|
||||
arguments:args
|
||||
binaryMessenger:_messenger];
|
||||
return webviewController;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLTWebViewController {
|
||||
WKWebView* _webView;
|
||||
int64_t _viewId;
|
||||
FlutterMethodChannel* _channel;
|
||||
NSString* _currentUrl;
|
||||
// The set of registered JavaScript channel names.
|
||||
NSMutableSet* _javaScriptChannelNames;
|
||||
FLTWKNavigationDelegate* _navigationDelegate;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
viewIdentifier:(int64_t)viewId
|
||||
arguments:(id _Nullable)args
|
||||
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
|
||||
if ([super init]) {
|
||||
_viewId = viewId;
|
||||
|
||||
NSString* channelName = [NSString stringWithFormat:@"plugins.flutter.io/webview_%lld", viewId];
|
||||
_channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger];
|
||||
_javaScriptChannelNames = [[NSMutableSet alloc] init];
|
||||
|
||||
WKUserContentController* userContentController = [[WKUserContentController alloc] init];
|
||||
if ([args[@"javascriptChannelNames"] isKindOfClass:[NSArray class]]) {
|
||||
NSArray* javaScriptChannelNames = args[@"javascriptChannelNames"];
|
||||
[_javaScriptChannelNames addObjectsFromArray:javaScriptChannelNames];
|
||||
[self registerJavaScriptChannels:_javaScriptChannelNames controller:userContentController];
|
||||
}
|
||||
|
||||
WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
|
||||
configuration.userContentController = userContentController;
|
||||
|
||||
_webView = [[WKWebView alloc] initWithFrame:frame configuration:configuration];
|
||||
_navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:_channel];
|
||||
_webView.navigationDelegate = _navigationDelegate;
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
|
||||
[weakSelf onMethodCall:call result:result];
|
||||
}];
|
||||
NSDictionary<NSString*, id>* settings = args[@"settings"];
|
||||
[self applySettings:settings];
|
||||
|
||||
NSString* initialUrl = args[@"initialUrl"];
|
||||
if ([initialUrl isKindOfClass:[NSString class]]) {
|
||||
[self loadUrl:initialUrl];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIView*)view {
|
||||
return _webView;
|
||||
}
|
||||
|
||||
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
if ([[call method] isEqualToString:@"updateSettings"]) {
|
||||
[self onUpdateSettings:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"loadUrl"]) {
|
||||
[self onLoadUrl:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"canGoBack"]) {
|
||||
[self onCanGoBack:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"canGoForward"]) {
|
||||
[self onCanGoForward:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"goBack"]) {
|
||||
[self onGoBack:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"goForward"]) {
|
||||
[self onGoForward:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"reload"]) {
|
||||
[self onReload:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"currentUrl"]) {
|
||||
[self onCurrentUrl:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"evaluateJavascript"]) {
|
||||
[self onEvaluateJavaScript:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"addJavascriptChannels"]) {
|
||||
[self onAddJavaScriptChannels:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"removeJavascriptChannels"]) {
|
||||
[self onRemoveJavaScriptChannels:call result:result];
|
||||
} else if ([[call method] isEqualToString:@"clearCache"]) {
|
||||
[self clearCache:result];
|
||||
} else {
|
||||
result(FlutterMethodNotImplemented);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onUpdateSettings:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
[self applySettings:[call arguments]];
|
||||
result(nil);
|
||||
}
|
||||
|
||||
- (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
if (![self loadRequest:[call arguments]]) {
|
||||
result([FlutterError
|
||||
errorWithCode:@"loadUrl_failed"
|
||||
message:@"Failed parsing the URL"
|
||||
details:[NSString stringWithFormat:@"Request was: '%@'", [call arguments]]]);
|
||||
} else {
|
||||
result(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onCanGoBack:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
BOOL canGoBack = [_webView canGoBack];
|
||||
result([NSNumber numberWithBool:canGoBack]);
|
||||
}
|
||||
|
||||
- (void)onCanGoForward:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
BOOL canGoForward = [_webView canGoForward];
|
||||
result([NSNumber numberWithBool:canGoForward]);
|
||||
}
|
||||
|
||||
- (void)onGoBack:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
[_webView goBack];
|
||||
result(nil);
|
||||
}
|
||||
|
||||
- (void)onGoForward:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
[_webView goForward];
|
||||
result(nil);
|
||||
}
|
||||
|
||||
- (void)onReload:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
[_webView reload];
|
||||
result(nil);
|
||||
}
|
||||
|
||||
- (void)onCurrentUrl:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
_currentUrl = [[_webView URL] absoluteString];
|
||||
result(_currentUrl);
|
||||
}
|
||||
|
||||
- (void)onEvaluateJavaScript:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
NSString* jsString = [call arguments];
|
||||
if (!jsString) {
|
||||
result([FlutterError errorWithCode:@"evaluateJavaScript_failed"
|
||||
message:@"JavaScript String cannot be null"
|
||||
details:nil]);
|
||||
return;
|
||||
}
|
||||
[_webView evaluateJavaScript:jsString
|
||||
completionHandler:^(_Nullable id evaluateResult, NSError* _Nullable error) {
|
||||
if (error) {
|
||||
result([FlutterError
|
||||
errorWithCode:@"evaluateJavaScript_failed"
|
||||
message:@"Failed evaluating JavaScript"
|
||||
details:[NSString stringWithFormat:@"JavaScript string was: '%@'\n%@",
|
||||
jsString, error]]);
|
||||
} else {
|
||||
result([NSString stringWithFormat:@"%@", evaluateResult]);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)onAddJavaScriptChannels:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
NSArray* channelNames = [call arguments];
|
||||
NSSet* channelNamesSet = [[NSSet alloc] initWithArray:channelNames];
|
||||
[_javaScriptChannelNames addObjectsFromArray:channelNames];
|
||||
[self registerJavaScriptChannels:channelNamesSet
|
||||
controller:_webView.configuration.userContentController];
|
||||
result(nil);
|
||||
}
|
||||
|
||||
- (void)onRemoveJavaScriptChannels:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
// WkWebView does not support removing a single user script, so instead we remove all
|
||||
// user scripts, all message handlers. And re-register channels that shouldn't be removed.
|
||||
[_webView.configuration.userContentController removeAllUserScripts];
|
||||
for (NSString* channelName in _javaScriptChannelNames) {
|
||||
[_webView.configuration.userContentController removeScriptMessageHandlerForName:channelName];
|
||||
}
|
||||
|
||||
NSArray* channelNamesToRemove = [call arguments];
|
||||
for (NSString* channelName in channelNamesToRemove) {
|
||||
[_javaScriptChannelNames removeObject:channelName];
|
||||
}
|
||||
|
||||
[self registerJavaScriptChannels:_javaScriptChannelNames
|
||||
controller:_webView.configuration.userContentController];
|
||||
result(nil);
|
||||
}
|
||||
|
||||
- (void)clearCache:(FlutterResult)result {
|
||||
if (@available(iOS 9.0, *)) {
|
||||
NSSet* cacheDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
|
||||
WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore];
|
||||
NSDate* dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
|
||||
[dataStore removeDataOfTypes:cacheDataTypes
|
||||
modifiedSince:dateFrom
|
||||
completionHandler:^{
|
||||
result(nil);
|
||||
}];
|
||||
} else {
|
||||
// support for iOS8 tracked in https://github.com/flutter/flutter/issues/27624.
|
||||
NSLog(@"Clearing cache is not supported for Flutter WebViews prior to iOS 9.");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applySettings:(NSDictionary<NSString*, id>*)settings {
|
||||
for (NSString* key in settings) {
|
||||
if ([key isEqualToString:@"jsMode"]) {
|
||||
NSNumber* mode = settings[key];
|
||||
[self updateJsMode:mode];
|
||||
} else if ([key isEqualToString:@"hasNavigationDelegate"]) {
|
||||
NSNumber* hasDartNavigationDelegate = settings[key];
|
||||
_navigationDelegate.hasDartNavigationDelegate = [hasDartNavigationDelegate boolValue];
|
||||
} else {
|
||||
NSLog(@"webview_flutter: unknown setting key: %@", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateJsMode:(NSNumber*)mode {
|
||||
WKPreferences* preferences = [[_webView configuration] preferences];
|
||||
switch ([mode integerValue]) {
|
||||
case 0: // disabled
|
||||
[preferences setJavaScriptEnabled:NO];
|
||||
break;
|
||||
case 1: // unrestricted
|
||||
[preferences setJavaScriptEnabled:YES];
|
||||
break;
|
||||
default:
|
||||
NSLog(@"webview_flutter: unknown JavaScript mode: %@", mode);
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)loadRequest:(NSDictionary<NSString*, id>*)request {
|
||||
if (!request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NSString* url = request[@"url"];
|
||||
if ([url isKindOfClass:[NSString class]]) {
|
||||
id headers = request[@"headers"];
|
||||
if ([headers isKindOfClass:[NSDictionary class]]) {
|
||||
return [self loadUrl:url withHeaders:headers];
|
||||
} else {
|
||||
return [self loadUrl:url];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
- (bool)loadUrl:(NSString*)url {
|
||||
return [self loadUrl:url withHeaders:[NSMutableDictionary dictionary]];
|
||||
}
|
||||
|
||||
- (bool)loadUrl:(NSString*)url withHeaders:(NSDictionary<NSString*, NSString*>*)headers {
|
||||
NSURL* nsUrl = [NSURL URLWithString:url];
|
||||
if (!nsUrl) {
|
||||
return false;
|
||||
}
|
||||
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:nsUrl];
|
||||
[request setAllHTTPHeaderFields:headers];
|
||||
[_webView loadRequest:request];
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)registerJavaScriptChannels:(NSSet*)channelNames
|
||||
controller:(WKUserContentController*)userContentController {
|
||||
for (NSString* channelName in channelNames) {
|
||||
FLTJavaScriptChannel* channel =
|
||||
[[FLTJavaScriptChannel alloc] initWithMethodChannel:_channel
|
||||
javaScriptChannelName:channelName];
|
||||
[userContentController addScriptMessageHandler:channel name:channelName];
|
||||
NSString* wrapperSource = [NSString
|
||||
stringWithFormat:@"window.%@ = webkit.messageHandlers.%@;", channelName, channelName];
|
||||
WKUserScript* wrapperScript =
|
||||
[[WKUserScript alloc] initWithSource:wrapperSource
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
||||
forMainFrameOnly:NO];
|
||||
[userContentController addUserScript:wrapperScript];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLTJavaScriptChannel : NSObject <WKScriptMessageHandler>
|
||||
|
||||
- (instancetype)initWithMethodChannel:(FlutterMethodChannel*)methodChannel
|
||||
javaScriptChannelName:(NSString*)javaScriptChannelName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "JavaScriptChannelHandler.h"
|
||||
|
||||
@implementation FLTJavaScriptChannel {
|
||||
FlutterMethodChannel* _methodChannel;
|
||||
NSString* _javaScriptChannelName;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMethodChannel:(FlutterMethodChannel*)methodChannel
|
||||
javaScriptChannelName:(NSString*)javaScriptChannelName {
|
||||
self = [super init];
|
||||
NSAssert(methodChannel != nil, @"methodChannel must not be null.");
|
||||
NSAssert(javaScriptChannelName != nil, @"javaScriptChannelName must not be null.");
|
||||
if (self) {
|
||||
_methodChannel = methodChannel;
|
||||
_javaScriptChannelName = javaScriptChannelName;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)userContentController:(WKUserContentController*)userContentController
|
||||
didReceiveScriptMessage:(WKScriptMessage*)message {
|
||||
NSAssert(_methodChannel != nil, @"Can't send a message to an unitialized JavaScript channel.");
|
||||
NSAssert(_javaScriptChannelName != nil,
|
||||
@"Can't send a message to an unitialized JavaScript channel.");
|
||||
NSDictionary* arguments = @{
|
||||
@"channel" : _javaScriptChannelName,
|
||||
@"message" : [NSString stringWithFormat:@"%@", message.body]
|
||||
};
|
||||
[_methodChannel invokeMethod:@"javascriptChannelMessage" arguments:arguments];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
|
||||
@interface FLTWebViewFlutterPlugin : NSObject <FlutterPlugin>
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
#import "WebViewFlutterPlugin.h"
|
||||
#import "FLTCookieManager.h"
|
||||
#import "FlutterWebView.h"
|
||||
|
||||
@implementation FLTWebViewFlutterPlugin
|
||||
|
||||
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
|
||||
FLTWebViewFactory* webviewFactory =
|
||||
[[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger];
|
||||
[registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"];
|
||||
[FLTCookieManager registerWithRegistrar:registrar];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'webview_flutter'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'A WebView Plugin for Flutter.'
|
||||
s.description = <<-DESC
|
||||
A WebView Plugin for Flutter.
|
||||
DESC
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.public_header_files = 'Classes/**/*.h'
|
||||
s.dependency 'Flutter'
|
||||
|
||||
s.ios.deployment_target = '8.0'
|
||||
end
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'webview_flutter.dart';
|
||||
|
||||
/// Interface for callbacks made by [WebViewPlatformController].
|
||||
///
|
||||
/// The webview plugin implements this class, and passes an instance to the [WebViewPlatformController].
|
||||
/// [WebViewPlatformController] is notifying this handler on events that happened on the platform's webview.
|
||||
abstract class WebViewPlatformCallbacksHandler {
|
||||
/// Invoked by [WebViewPlatformController] when a JavaScript channel message is received.
|
||||
void onJavaScriptChannelMessage(String channel, String message);
|
||||
|
||||
/// Invoked by [WebViewPlatformController] when a navigation request is pending.
|
||||
///
|
||||
/// If true is returned the navigation is allowed, otherwise it is blocked.
|
||||
bool onNavigationRequest({String url, bool isForMainFrame});
|
||||
|
||||
/// Invoked by [WebViewPlatformController] when a page has finished loading.
|
||||
void onPageFinished(String url);
|
||||
}
|
||||
|
||||
/// Interface for talking to the webview's platform implementation.
|
||||
///
|
||||
/// An instance implementing this interface is passed to the `onWebViewPlatformCreated` callback that is
|
||||
/// passed to [WebViewPlatformBuilder#onWebViewPlatformCreated].
|
||||
abstract class WebViewPlatformController {
|
||||
/// Creates a new WebViewPlatform.
|
||||
///
|
||||
/// Callbacks made by the WebView will be delegated to `handler`.
|
||||
///
|
||||
/// The `handler` parameter must not be null.
|
||||
WebViewPlatformController(WebViewPlatformCallbacksHandler handler);
|
||||
|
||||
/// Loads the specified URL.
|
||||
///
|
||||
/// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will
|
||||
/// be added as key value pairs of HTTP headers for the request.
|
||||
///
|
||||
/// `url` must not be null.
|
||||
///
|
||||
/// Throws an ArgumentError if `url` is not a valid URL string.
|
||||
Future<void> loadUrl(
|
||||
String url,
|
||||
Map<String, String> headers,
|
||||
) {
|
||||
throw UnimplementedError(
|
||||
"WebView loadUrl is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Updates the webview settings.
|
||||
///
|
||||
/// Any non null field in `settings` will be set as the new setting value.
|
||||
/// All null fields in `settings` are ignored.
|
||||
Future<void> updateSettings(WebSettings setting) {
|
||||
throw UnimplementedError(
|
||||
"WebView updateSettings is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Accessor to the current URL that the WebView is displaying.
|
||||
///
|
||||
/// If no URL was ever loaded, returns `null`.
|
||||
Future<String> currentUrl() {
|
||||
throw UnimplementedError(
|
||||
"WebView currentUrl is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Checks whether there's a back history item.
|
||||
Future<bool> canGoBack() {
|
||||
throw UnimplementedError(
|
||||
"WebView canGoBack is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Checks whether there's a forward history item.
|
||||
Future<bool> canGoForward() {
|
||||
throw UnimplementedError(
|
||||
"WebView canGoForward is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Goes back in the history of this WebView.
|
||||
///
|
||||
/// If there is no back history item this is a no-op.
|
||||
Future<void> goBack() {
|
||||
throw UnimplementedError(
|
||||
"WebView goBack is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Goes forward in the history of this WebView.
|
||||
///
|
||||
/// If there is no forward history item this is a no-op.
|
||||
Future<void> goForward() {
|
||||
throw UnimplementedError(
|
||||
"WebView goForward is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Reloads the current URL.
|
||||
Future<void> reload() {
|
||||
throw UnimplementedError(
|
||||
"WebView reload is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Clears all caches used by the [WebView].
|
||||
///
|
||||
/// The following caches are cleared:
|
||||
/// 1. Browser HTTP Cache.
|
||||
/// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches.
|
||||
/// These are not yet supported in iOS WkWebView. Service workers tend to use this cache.
|
||||
/// 3. Application cache.
|
||||
/// 4. Local Storage.
|
||||
Future<void> clearCache() {
|
||||
throw UnimplementedError(
|
||||
"WebView clearCache is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Evaluates a JavaScript expression in the context of the current page.
|
||||
///
|
||||
/// The Future completes with an error if a JavaScript error occurred, or if the type of the
|
||||
/// evaluated expression is not supported(e.g on iOS not all non primitive type can be evaluated).
|
||||
Future<String> evaluateJavascript(String javascriptString) {
|
||||
throw UnimplementedError(
|
||||
"WebView evaluateJavascript is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Adds new JavaScript channels to the set of enabled channels.
|
||||
///
|
||||
/// For each value in this list the platform's webview should make sure that a corresponding
|
||||
/// property with a postMessage method is set on `window`. For example for a JavaScript channel
|
||||
/// named `Foo` it should be possible for JavaScript code executing in the webview to do
|
||||
///
|
||||
/// ```javascript
|
||||
/// Foo.postMessage('hello');
|
||||
/// ```
|
||||
///
|
||||
/// See also: [CreationParams.javascriptChannelNames].
|
||||
Future<void> addJavascriptChannels(Set<String> javascriptChannelNames) {
|
||||
throw UnimplementedError(
|
||||
"WebView addJavascriptChannels is not implemented on the current platform");
|
||||
}
|
||||
|
||||
/// Removes JavaScript channel names from the set of enabled channels.
|
||||
///
|
||||
/// This disables channels that were previously enabled by [addJavaScriptChannels] or through
|
||||
/// [CreationParams.javascriptChannelNames].
|
||||
Future<void> removeJavascriptChannels(Set<String> javascriptChannelNames) {
|
||||
throw UnimplementedError(
|
||||
"WebView removeJavascriptChannels is not implemented on the current platform");
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings for configuring a WebViewPlatform.
|
||||
///
|
||||
/// Initial settings are passed as part of [CreationParams], settings updates are sent with
|
||||
/// [WebViewPlatform#updateSettings].
|
||||
class WebSettings {
|
||||
WebSettings({
|
||||
this.javascriptMode,
|
||||
this.hasNavigationDelegate,
|
||||
this.debuggingEnabled,
|
||||
});
|
||||
|
||||
/// The JavaScript execution mode to be used by the webview.
|
||||
final JavascriptMode javascriptMode;
|
||||
|
||||
/// Whether the [WebView] has a [NavigationDelegate] set.
|
||||
final bool hasNavigationDelegate;
|
||||
|
||||
/// Whether to enable the platform's webview content debugging tools.
|
||||
///
|
||||
/// See also: [WebView.debuggingEnabled].
|
||||
final bool debuggingEnabled;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration to use when creating a new [WebViewPlatformController].
|
||||
class CreationParams {
|
||||
CreationParams(
|
||||
{this.initialUrl, this.webSettings, this.javascriptChannelNames});
|
||||
|
||||
/// The initialUrl to load in the webview.
|
||||
///
|
||||
/// When null the webview will be created without loading any page.
|
||||
final String initialUrl;
|
||||
|
||||
/// The initial [WebSettings] for the new webview.
|
||||
///
|
||||
/// This can later be updated with [WebViewPlatformController.updateSettings].
|
||||
final WebSettings webSettings;
|
||||
|
||||
/// The initial set of JavaScript channels that are configured for this webview.
|
||||
///
|
||||
/// For each value in this set the platform's webview should make sure that a corresponding
|
||||
/// property with a postMessage method is set on `window`. For example for a JavaScript channel
|
||||
/// named `Foo` it should be possible for JavaScript code executing in the webview to do
|
||||
///
|
||||
/// ```javascript
|
||||
/// Foo.postMessage('hello');
|
||||
/// ```
|
||||
// TODO(amirh): describe what should happen when postMessage is called once that code is migrated
|
||||
// to PlatformWebView.
|
||||
final Set<String> javascriptChannelNames;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames)';
|
||||
}
|
||||
}
|
||||
|
||||
typedef WebViewPlatformCreatedCallback = void Function(
|
||||
WebViewPlatformController webViewPlatformController);
|
||||
|
||||
/// Interface for a platform implementation of a WebView.
|
||||
///
|
||||
/// [WebView.platform] controls the builder that is used by [WebView].
|
||||
/// [AndroidWebViewPlatform] and [CupertinoWebViewPlatform] are the default implementations
|
||||
/// for Android and iOS respectively.
|
||||
abstract class WebViewPlatform {
|
||||
/// Builds a new WebView.
|
||||
///
|
||||
/// Returns a Widget tree that embeds the created webview.
|
||||
///
|
||||
/// `creationParams` are the initial parameters used to setup the webview.
|
||||
///
|
||||
/// `webViewPlatformHandler` will be used for handling callbacks that are made by the created
|
||||
/// [WebViewPlatformController].
|
||||
///
|
||||
/// `onWebViewPlatformCreated` will be invoked after the platform specific [WebViewPlatformController]
|
||||
/// implementation is created with the [WebViewPlatformController] instance as a parameter.
|
||||
///
|
||||
/// `gestureRecognizers` specifies which gestures should be consumed by the web view.
|
||||
/// It is possible for other gesture recognizers to be competing with the web view on pointer
|
||||
/// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle
|
||||
/// vertical drags. The web view will claim gestures that are recognized by any of the
|
||||
/// recognizers on this list.
|
||||
/// When `gestureRecognizers` is empty or null, the web view will only handle pointer events for gestures that
|
||||
/// were not claimed by any other gesture recognizer.
|
||||
///
|
||||
/// `webViewPlatformHandler` must not be null.
|
||||
Widget build({
|
||||
BuildContext context,
|
||||
// TODO(amirh): convert this to be the actual parameters.
|
||||
// I'm starting without it as the PR is starting to become pretty big.
|
||||
// I'll followup with the conversion PR.
|
||||
CreationParams creationParams,
|
||||
@required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
|
||||
WebViewPlatformCreatedCallback onWebViewPlatformCreated,
|
||||
Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
|
||||
});
|
||||
|
||||
/// Clears all cookies for all [WebView] instances.
|
||||
///
|
||||
/// Returns true if cookies were present before clearing, else false.
|
||||
Future<bool> clearCookies() {
|
||||
throw UnimplementedError(
|
||||
"WebView clearCookies is not implemented on the current platform");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../platform_interface.dart';
|
||||
import 'webview_method_channel.dart';
|
||||
|
||||
/// Builds an Android webview.
|
||||
///
|
||||
/// This is used as the default implementation for [WebView.platform] on Android. It uses
|
||||
/// an [AndroidView] to embed the webview in the widget hierarchy, and uses a method channel to
|
||||
/// communicate with the platform code.
|
||||
class AndroidWebView implements WebViewPlatform {
|
||||
@override
|
||||
Widget build({
|
||||
BuildContext context,
|
||||
CreationParams creationParams,
|
||||
@required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
|
||||
WebViewPlatformCreatedCallback onWebViewPlatformCreated,
|
||||
Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
|
||||
}) {
|
||||
assert(webViewPlatformCallbacksHandler != null);
|
||||
return GestureDetector(
|
||||
// We prevent text selection by intercepting the long press event.
|
||||
// This is a temporary stop gap due to issues with text selection on Android:
|
||||
// https://github.com/flutter/flutter/issues/24585 - the text selection
|
||||
// dialog is not responding to touch events.
|
||||
// https://github.com/flutter/flutter/issues/24584 - the text selection
|
||||
// handles are not showing.
|
||||
// TODO(amirh): remove this when the issues above are fixed.
|
||||
onLongPress: () {},
|
||||
excludeFromSemantics: true,
|
||||
child: AndroidView(
|
||||
viewType: 'plugins.flutter.io/webview',
|
||||
onPlatformViewCreated: (int id) {
|
||||
if (onWebViewPlatformCreated == null) {
|
||||
return;
|
||||
}
|
||||
onWebViewPlatformCreated(MethodChannelWebViewPlatform(
|
||||
id, webViewPlatformCallbacksHandler));
|
||||
},
|
||||
gestureRecognizers: gestureRecognizers,
|
||||
// WebView content is not affected by the Android view's layout direction,
|
||||
// we explicitly set it here so that the widget doesn't require an ambient
|
||||
// directionality.
|
||||
layoutDirection: TextDirection.rtl,
|
||||
creationParams:
|
||||
MethodChannelWebViewPlatform.creationParamsToMap(creationParams),
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> clearCookies() => MethodChannelWebViewPlatform.clearCookies();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../platform_interface.dart';
|
||||
import 'webview_method_channel.dart';
|
||||
|
||||
/// Builds an iOS webview.
|
||||
///
|
||||
/// This is used as the default implementation for [WebView.platform] on iOS. It uses
|
||||
/// a [UiKitView] to embed the webview in the widget hierarchy, and uses a method channel to
|
||||
/// communicate with the platform code.
|
||||
class CupertinoWebView implements WebViewPlatform {
|
||||
@override
|
||||
Widget build({
|
||||
BuildContext context,
|
||||
CreationParams creationParams,
|
||||
@required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
|
||||
WebViewPlatformCreatedCallback onWebViewPlatformCreated,
|
||||
Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
|
||||
}) {
|
||||
return UiKitView(
|
||||
viewType: 'plugins.flutter.io/webview',
|
||||
onPlatformViewCreated: (int id) {
|
||||
if (onWebViewPlatformCreated == null) {
|
||||
return;
|
||||
}
|
||||
onWebViewPlatformCreated(
|
||||
MethodChannelWebViewPlatform(id, webViewPlatformCallbacksHandler));
|
||||
},
|
||||
gestureRecognizers: gestureRecognizers,
|
||||
creationParams:
|
||||
MethodChannelWebViewPlatform.creationParamsToMap(creationParams),
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> clearCookies() => MethodChannelWebViewPlatform.clearCookies();
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../platform_interface.dart';
|
||||
|
||||
/// A [WebViewPlatformController] that uses a method channel to control the webview.
|
||||
class MethodChannelWebViewPlatform implements WebViewPlatformController {
|
||||
MethodChannelWebViewPlatform(int id, this._platformCallbacksHandler)
|
||||
: assert(_platformCallbacksHandler != null),
|
||||
_channel = MethodChannel('plugins.flutter.io/webview_$id') {
|
||||
_channel.setMethodCallHandler(_onMethodCall);
|
||||
}
|
||||
|
||||
final WebViewPlatformCallbacksHandler _platformCallbacksHandler;
|
||||
|
||||
final MethodChannel _channel;
|
||||
|
||||
static const MethodChannel _cookieManagerChannel =
|
||||
MethodChannel('plugins.flutter.io/cookie_manager');
|
||||
|
||||
Future<bool> _onMethodCall(MethodCall call) async {
|
||||
switch (call.method) {
|
||||
case 'javascriptChannelMessage':
|
||||
final String channel = call.arguments['channel'];
|
||||
final String message = call.arguments['message'];
|
||||
_platformCallbacksHandler.onJavaScriptChannelMessage(channel, message);
|
||||
return true;
|
||||
case 'navigationRequest':
|
||||
return _platformCallbacksHandler.onNavigationRequest(
|
||||
url: call.arguments['url'],
|
||||
isForMainFrame: call.arguments['isForMainFrame'],
|
||||
);
|
||||
case 'onPageFinished':
|
||||
_platformCallbacksHandler.onPageFinished(call.arguments['url']);
|
||||
return null;
|
||||
}
|
||||
throw MissingPluginException(
|
||||
'${call.method} was invoked but has no handler');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadUrl(
|
||||
String url,
|
||||
Map<String, String> headers,
|
||||
) async {
|
||||
assert(url != null);
|
||||
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
|
||||
// https://github.com/flutter/flutter/issues/26431
|
||||
// ignore: strong_mode_implicit_dynamic_method
|
||||
return _channel.invokeMethod('loadUrl', <String, dynamic>{
|
||||
'url': url,
|
||||
'headers': headers,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> currentUrl() => _channel.invokeMethod('currentUrl');
|
||||
|
||||
@override
|
||||
Future<bool> canGoBack() => _channel.invokeMethod("canGoBack");
|
||||
|
||||
@override
|
||||
Future<bool> canGoForward() => _channel.invokeMethod("canGoForward");
|
||||
|
||||
@override
|
||||
Future<void> goBack() => _channel.invokeMethod("goBack");
|
||||
|
||||
@override
|
||||
Future<void> goForward() => _channel.invokeMethod("goForward");
|
||||
|
||||
@override
|
||||
Future<void> reload() => _channel.invokeMethod("reload");
|
||||
|
||||
@override
|
||||
Future<void> clearCache() => _channel.invokeMethod("clearCache");
|
||||
|
||||
@override
|
||||
Future<void> updateSettings(WebSettings settings) {
|
||||
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
|
||||
// https://github.com/flutter/flutter/issues/26431
|
||||
// ignore: strong_mode_implicit_dynamic_method
|
||||
final Map<String, dynamic> updatesMap = _webSettingsToMap(settings);
|
||||
if (updatesMap.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return _channel.invokeMethod('updateSettings', updatesMap);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> evaluateJavascript(String javascriptString) {
|
||||
return _channel.invokeMethod('evaluateJavascript', javascriptString);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addJavascriptChannels(Set<String> javascriptChannelNames) {
|
||||
return _channel.invokeMethod(
|
||||
'addJavascriptChannels', javascriptChannelNames.toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeJavascriptChannels(Set<String> javascriptChannelNames) {
|
||||
return _channel.invokeMethod(
|
||||
'removeJavascriptChannels', javascriptChannelNames.toList());
|
||||
}
|
||||
|
||||
/// Method channel mplementation for [WebViewPlatform.clearCookies].
|
||||
static Future<bool> clearCookies() {
|
||||
return _cookieManagerChannel
|
||||
// TODO(amirh): remove this when the invokeMethod update makes it to stable Flutter.
|
||||
// https://github.com/flutter/flutter/issues/26431
|
||||
// ignore: strong_mode_implicit_dynamic_method
|
||||
.invokeMethod('clearCookies')
|
||||
.then<bool>((dynamic result) => result);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _webSettingsToMap(WebSettings settings) {
|
||||
final Map<String, dynamic> map = <String, dynamic>{};
|
||||
void _addIfNonNull(String key, dynamic value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
map[key] = value;
|
||||
}
|
||||
|
||||
_addIfNonNull('jsMode', settings.javascriptMode?.index);
|
||||
_addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate);
|
||||
_addIfNonNull('debuggingEnabled', settings.debuggingEnabled);
|
||||
return map;
|
||||
}
|
||||
|
||||
/// Converts a [CreationParams] object to a map as expected by `platform_views` channel.
|
||||
///
|
||||
/// This is used for the `creationParams` argument of the platform views created by
|
||||
/// [AndroidWebViewBuilder] and [CupertinoWebViewBuilder].
|
||||
static Map<String, dynamic> creationParamsToMap(
|
||||
CreationParams creationParams) {
|
||||
return <String, dynamic>{
|
||||
'initialUrl': creationParams.initialUrl,
|
||||
'settings': _webSettingsToMap(creationParams.webSettings),
|
||||
'javascriptChannelNames': creationParams.javascriptChannelNames.toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'platform_interface.dart';
|
||||
import 'src/webview_android.dart';
|
||||
import 'src/webview_cupertino.dart';
|
||||
|
||||
typedef void WebViewCreatedCallback(WebViewController controller);
|
||||
|
||||
enum JavascriptMode {
|
||||
/// JavaScript execution is disabled.
|
||||
disabled,
|
||||
|
||||
/// JavaScript execution is not restricted.
|
||||
unrestricted,
|
||||
}
|
||||
|
||||
/// A message that was sent by JavaScript code running in a [WebView].
|
||||
class JavascriptMessage {
|
||||
/// Constructs a JavaScript message object.
|
||||
///
|
||||
/// The `message` parameter must not be null.
|
||||
const JavascriptMessage(this.message) : assert(message != null);
|
||||
|
||||
/// The contents of the message that was sent by the JavaScript code.
|
||||
final String message;
|
||||
}
|
||||
|
||||
/// Callback type for handling messages sent from Javascript running in a web view.
|
||||
typedef void JavascriptMessageHandler(JavascriptMessage message);
|
||||
|
||||
/// Information about a navigation action that is about to be executed.
|
||||
class NavigationRequest {
|
||||
NavigationRequest._({this.url, this.isForMainFrame});
|
||||
|
||||
/// The URL that will be loaded if the navigation is executed.
|
||||
final String url;
|
||||
|
||||
/// Whether the navigation request is to be loaded as the main frame.
|
||||
final bool isForMainFrame;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType(url: $url, isForMainFrame: $isForMainFrame)';
|
||||
}
|
||||
}
|
||||
|
||||
/// A decision on how to handle a navigation request.
|
||||
enum NavigationDecision {
|
||||
/// Prevent the navigation from taking place.
|
||||
prevent,
|
||||
|
||||
/// Allow the navigation to take place.
|
||||
navigate,
|
||||
}
|
||||
|
||||
/// Decides how to handle a specific navigation request.
|
||||
///
|
||||
/// The returned [NavigationDecision] determines how the navigation described by
|
||||
/// `navigation` should be handled.
|
||||
///
|
||||
/// See also: [WebView.navigationDelegate].
|
||||
typedef NavigationDecision NavigationDelegate(NavigationRequest navigation);
|
||||
|
||||
/// Signature for when a [WebView] has finished loading a page.
|
||||
typedef void PageFinishedCallback(String url);
|
||||
|
||||
final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9]*\$');
|
||||
|
||||
/// A named channel for receiving messaged from JavaScript code running inside a web view.
|
||||
class JavascriptChannel {
|
||||
/// Constructs a Javascript channel.
|
||||
///
|
||||
/// The parameters `name` and `onMessageReceived` must not be null.
|
||||
JavascriptChannel({
|
||||
@required this.name,
|
||||
@required this.onMessageReceived,
|
||||
}) : assert(name != null),
|
||||
assert(onMessageReceived != null),
|
||||
assert(_validChannelNames.hasMatch(name));
|
||||
|
||||
/// The channel's name.
|
||||
///
|
||||
/// Passing this channel object as part of a [WebView.javascriptChannels] adds a channel object to
|
||||
/// the Javascript window object's property named `name`.
|
||||
///
|
||||
/// The name must start with a letter or underscore(_), followed by any combination of those
|
||||
/// characters plus digits.
|
||||
///
|
||||
/// Note that any JavaScript existing `window` property with this name will be overriden.
|
||||
///
|
||||
/// See also [WebView.javascriptChannels] for more details on the channel registration mechanism.
|
||||
final String name;
|
||||
|
||||
/// A callback that's invoked when a message is received through the channel.
|
||||
final JavascriptMessageHandler onMessageReceived;
|
||||
}
|
||||
|
||||
/// A web view widget for showing html content.
|
||||
class WebView extends StatefulWidget {
|
||||
/// Creates a new web view.
|
||||
///
|
||||
/// The web view can be controlled using a `WebViewController` that is passed to the
|
||||
/// `onWebViewCreated` callback once the web view is created.
|
||||
///
|
||||
/// The `javascriptMode` parameter must not be null.
|
||||
const WebView({
|
||||
Key key,
|
||||
this.onWebViewCreated,
|
||||
this.initialUrl,
|
||||
this.javascriptMode = JavascriptMode.disabled,
|
||||
this.javascriptChannels,
|
||||
this.navigationDelegate,
|
||||
this.gestureRecognizers,
|
||||
this.onPageFinished,
|
||||
this.debuggingEnabled = false,
|
||||
}) : assert(javascriptMode != null),
|
||||
super(key: key);
|
||||
|
||||
static WebViewPlatform _platform;
|
||||
|
||||
/// Sets a custom [WebViewPlatform].
|
||||
///
|
||||
/// This property can be set to use a custom platform implementation for WebViews.
|
||||
///
|
||||
/// Setting `platform` doesn't affect [WebView]s that were already created.
|
||||
///
|
||||
/// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.
|
||||
static set platform(WebViewPlatform platform) {
|
||||
_platform = platform;
|
||||
}
|
||||
|
||||
/// The WebView platform that's used by this WebView.
|
||||
///
|
||||
/// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.
|
||||
static WebViewPlatform get platform {
|
||||
if (_platform == null) {
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
_platform = AndroidWebView();
|
||||
break;
|
||||
case TargetPlatform.iOS:
|
||||
_platform = CupertinoWebView();
|
||||
break;
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
"Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one");
|
||||
}
|
||||
}
|
||||
return _platform;
|
||||
}
|
||||
|
||||
/// If not null invoked once the web view is created.
|
||||
final WebViewCreatedCallback onWebViewCreated;
|
||||
|
||||
/// Which gestures should be consumed by the web view.
|
||||
///
|
||||
/// It is possible for other gesture recognizers to be competing with the web view on pointer
|
||||
/// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle
|
||||
/// vertical drags. The web view will claim gestures that are recognized by any of the
|
||||
/// recognizers on this list.
|
||||
///
|
||||
/// When this set is empty or null, the web view will only handle pointer events for gestures that
|
||||
/// were not claimed by any other gesture recognizer.
|
||||
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
|
||||
|
||||
/// The initial URL to load.
|
||||
final String initialUrl;
|
||||
|
||||
/// Whether Javascript execution is enabled.
|
||||
final JavascriptMode javascriptMode;
|
||||
|
||||
/// The set of [JavascriptChannel]s available to JavaScript code running in the web view.
|
||||
///
|
||||
/// For each [JavascriptChannel] in the set, a channel object is made available for the
|
||||
/// JavaScript code in a window property named [JavascriptChannel.name].
|
||||
/// The JavaScript code can then call `postMessage` on that object to send a message that will be
|
||||
/// passed to [JavascriptChannel.onMessageReceived].
|
||||
///
|
||||
/// For example for the following JavascriptChannel:
|
||||
///
|
||||
/// ```dart
|
||||
/// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); });
|
||||
/// ```
|
||||
///
|
||||
/// JavaScript code can call:
|
||||
///
|
||||
/// ```javascript
|
||||
/// Print.postMessage('Hello');
|
||||
/// ```
|
||||
///
|
||||
/// To asynchronously invoke the message handler which will print the message to standard output.
|
||||
///
|
||||
/// Adding a new JavaScript channel only takes affect after the next page is loaded.
|
||||
///
|
||||
/// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple
|
||||
/// channels in the list.
|
||||
///
|
||||
/// A null value is equivalent to an empty set.
|
||||
final Set<JavascriptChannel> javascriptChannels;
|
||||
|
||||
/// A delegate function that decides how to handle navigation actions.
|
||||
///
|
||||
/// When a navigation is initiated by the WebView (e.g when a user clicks a link)
|
||||
/// this delegate is called and has to decide how to proceed with the navigation.
|
||||
///
|
||||
/// See [NavigationDecision] for possible decisions the delegate can take.
|
||||
///
|
||||
/// When null all navigation actions are allowed.
|
||||
///
|
||||
/// Caveats on Android:
|
||||
///
|
||||
/// * Navigation actions targeted to the main frame can be intercepted,
|
||||
/// navigation actions targeted to subframes are allowed regardless of the value
|
||||
/// returned by this delegate.
|
||||
/// * Setting a navigationDelegate makes the WebView treat all navigations as if they were
|
||||
/// triggered by a user gesture, this disables some of Chromium's security mechanisms.
|
||||
/// A navigationDelegate should only be set when loading trusted content.
|
||||
/// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have
|
||||
/// a later version):
|
||||
/// * When a navigationDelegate is set pages with frames are not properly handled by the
|
||||
/// webview, and frames will be opened in the main frame.
|
||||
/// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header.
|
||||
final NavigationDelegate navigationDelegate;
|
||||
|
||||
/// Invoked when a page has finished loading.
|
||||
///
|
||||
/// This is invoked only for the main frame.
|
||||
///
|
||||
/// When [onPageFinished] is invoked on Android, the page being rendered may
|
||||
/// not be updated yet.
|
||||
///
|
||||
/// When invoked on iOS or Android, any Javascript code that is embedded
|
||||
/// directly in the HTML has been loaded and code injected with
|
||||
/// [WebViewController.evaluateJavascript] can assume this.
|
||||
final PageFinishedCallback onPageFinished;
|
||||
|
||||
/// Controls whether WebView debugging is enabled.
|
||||
///
|
||||
/// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/).
|
||||
///
|
||||
/// WebView debugging is enabled by default in dev builds on iOS.
|
||||
///
|
||||
/// To debug WebViews on iOS:
|
||||
/// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.)
|
||||
/// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> <your webview page>
|
||||
///
|
||||
/// By default `debuggingEnabled` is false.
|
||||
final bool debuggingEnabled;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _WebViewState();
|
||||
}
|
||||
|
||||
class _WebViewState extends State<WebView> {
|
||||
final Completer<WebViewController> _controller =
|
||||
Completer<WebViewController>();
|
||||
|
||||
_PlatformCallbacksHandler _platformCallbacksHandler;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WebView.platform.build(
|
||||
context: context,
|
||||
onWebViewPlatformCreated: _onWebViewPlatformCreated,
|
||||
webViewPlatformCallbacksHandler: _platformCallbacksHandler,
|
||||
gestureRecognizers: widget.gestureRecognizers,
|
||||
creationParams: _creationParamsfromWidget(widget),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_assertJavascriptChannelNamesAreUnique();
|
||||
_platformCallbacksHandler = _PlatformCallbacksHandler(widget);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(WebView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_assertJavascriptChannelNamesAreUnique();
|
||||
_controller.future.then((WebViewController controller) {
|
||||
_platformCallbacksHandler._widget = widget;
|
||||
controller._updateWidget(widget);
|
||||
});
|
||||
}
|
||||
|
||||
void _onWebViewPlatformCreated(WebViewPlatformController webViewPlatform) {
|
||||
final WebViewController controller =
|
||||
WebViewController._(widget, webViewPlatform, _platformCallbacksHandler);
|
||||
_controller.complete(controller);
|
||||
if (widget.onWebViewCreated != null) {
|
||||
widget.onWebViewCreated(controller);
|
||||
}
|
||||
}
|
||||
|
||||
void _assertJavascriptChannelNamesAreUnique() {
|
||||
if (widget.javascriptChannels == null ||
|
||||
widget.javascriptChannels.isEmpty) {
|
||||
return;
|
||||
}
|
||||
assert(_extractChannelNames(widget.javascriptChannels).length ==
|
||||
widget.javascriptChannels.length);
|
||||
}
|
||||
}
|
||||
|
||||
CreationParams _creationParamsfromWidget(WebView widget) {
|
||||
return CreationParams(
|
||||
initialUrl: widget.initialUrl,
|
||||
webSettings: _webSettingsFromWidget(widget),
|
||||
javascriptChannelNames: _extractChannelNames(widget.javascriptChannels),
|
||||
);
|
||||
}
|
||||
|
||||
WebSettings _webSettingsFromWidget(WebView widget) {
|
||||
return WebSettings(
|
||||
javascriptMode: widget.javascriptMode,
|
||||
hasNavigationDelegate: widget.navigationDelegate != null,
|
||||
debuggingEnabled: widget.debuggingEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
// This method assumes that no fields in `currentValue` are null.
|
||||
WebSettings _clearUnchangedWebSettings(
|
||||
WebSettings currentValue, WebSettings newValue) {
|
||||
assert(currentValue.javascriptMode != null);
|
||||
assert(currentValue.hasNavigationDelegate != null);
|
||||
assert(currentValue.debuggingEnabled != null);
|
||||
assert(newValue.javascriptMode != null);
|
||||
assert(newValue.hasNavigationDelegate != null);
|
||||
assert(newValue.debuggingEnabled != null);
|
||||
JavascriptMode javascriptMode;
|
||||
bool hasNavigationDelegate;
|
||||
bool debuggingEnabled;
|
||||
if (currentValue.javascriptMode != newValue.javascriptMode) {
|
||||
javascriptMode = newValue.javascriptMode;
|
||||
}
|
||||
if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) {
|
||||
hasNavigationDelegate = newValue.hasNavigationDelegate;
|
||||
}
|
||||
if (currentValue.debuggingEnabled != newValue.debuggingEnabled) {
|
||||
debuggingEnabled = newValue.debuggingEnabled;
|
||||
}
|
||||
|
||||
return WebSettings(
|
||||
javascriptMode: javascriptMode,
|
||||
hasNavigationDelegate: hasNavigationDelegate,
|
||||
debuggingEnabled: debuggingEnabled);
|
||||
}
|
||||
|
||||
Set<String> _extractChannelNames(Set<JavascriptChannel> channels) {
|
||||
final Set<String> channelNames = channels == null
|
||||
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
|
||||
// ignore: prefer_collection_literals
|
||||
? Set<String>()
|
||||
: channels.map((JavascriptChannel channel) => channel.name).toSet();
|
||||
return channelNames;
|
||||
}
|
||||
|
||||
class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
|
||||
_PlatformCallbacksHandler(this._widget) {
|
||||
_updateJavascriptChannelsFromSet(_widget.javascriptChannels);
|
||||
}
|
||||
|
||||
WebView _widget;
|
||||
|
||||
// Maps a channel name to a channel.
|
||||
final Map<String, JavascriptChannel> _javascriptChannels =
|
||||
<String, JavascriptChannel>{};
|
||||
|
||||
@override
|
||||
void onJavaScriptChannelMessage(String channel, String message) {
|
||||
_javascriptChannels[channel].onMessageReceived(JavascriptMessage(message));
|
||||
}
|
||||
|
||||
@override
|
||||
bool onNavigationRequest({String url, bool isForMainFrame}) {
|
||||
final NavigationRequest request =
|
||||
NavigationRequest._(url: url, isForMainFrame: isForMainFrame);
|
||||
final bool allowNavigation = _widget.navigationDelegate == null ||
|
||||
_widget.navigationDelegate(request) == NavigationDecision.navigate;
|
||||
return allowNavigation;
|
||||
}
|
||||
|
||||
@override
|
||||
void onPageFinished(String url) {
|
||||
if (_widget.onPageFinished != null) {
|
||||
_widget.onPageFinished(url);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateJavascriptChannelsFromSet(Set<JavascriptChannel> channels) {
|
||||
_javascriptChannels.clear();
|
||||
if (channels == null) {
|
||||
return;
|
||||
}
|
||||
for (JavascriptChannel channel in channels) {
|
||||
_javascriptChannels[channel.name] = channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls a [WebView].
|
||||
///
|
||||
/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated]
|
||||
/// callback for a [WebView] widget.
|
||||
class WebViewController {
|
||||
WebViewController._(
|
||||
this._widget,
|
||||
this._webViewPlatformController,
|
||||
this._platformCallbacksHandler,
|
||||
) : assert(_webViewPlatformController != null) {
|
||||
_settings = _webSettingsFromWidget(_widget);
|
||||
}
|
||||
|
||||
final WebViewPlatformController _webViewPlatformController;
|
||||
|
||||
final _PlatformCallbacksHandler _platformCallbacksHandler;
|
||||
|
||||
WebSettings _settings;
|
||||
|
||||
WebView _widget;
|
||||
|
||||
/// Loads the specified URL.
|
||||
///
|
||||
/// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will
|
||||
/// be added as key value pairs of HTTP headers for the request.
|
||||
///
|
||||
/// `url` must not be null.
|
||||
///
|
||||
/// Throws an ArgumentError if `url` is not a valid URL string.
|
||||
Future<void> loadUrl(
|
||||
String url, {
|
||||
Map<String, String> headers,
|
||||
}) async {
|
||||
assert(url != null);
|
||||
_validateUrlString(url);
|
||||
return _webViewPlatformController.loadUrl(url, headers);
|
||||
}
|
||||
|
||||
/// Accessor to the current URL that the WebView is displaying.
|
||||
///
|
||||
/// If [WebView.initialUrl] was never specified, returns `null`.
|
||||
/// Note that this operation is asynchronous, and it is possible that the
|
||||
/// current URL changes again by the time this function returns (in other
|
||||
/// words, by the time this future completes, the WebView may be displaying a
|
||||
/// different URL).
|
||||
Future<String> currentUrl() {
|
||||
return _webViewPlatformController.currentUrl();
|
||||
}
|
||||
|
||||
/// Checks whether there's a back history item.
|
||||
///
|
||||
/// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has
|
||||
/// changed by the time the future completed.
|
||||
Future<bool> canGoBack() {
|
||||
return _webViewPlatformController.canGoBack();
|
||||
}
|
||||
|
||||
/// Checks whether there's a forward history item.
|
||||
///
|
||||
/// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has
|
||||
/// changed by the time the future completed.
|
||||
Future<bool> canGoForward() {
|
||||
return _webViewPlatformController.canGoForward();
|
||||
}
|
||||
|
||||
/// Goes back in the history of this WebView.
|
||||
///
|
||||
/// If there is no back history item this is a no-op.
|
||||
Future<void> goBack() {
|
||||
return _webViewPlatformController.goBack();
|
||||
}
|
||||
|
||||
/// Goes forward in the history of this WebView.
|
||||
///
|
||||
/// If there is no forward history item this is a no-op.
|
||||
Future<void> goForward() {
|
||||
return _webViewPlatformController.goForward();
|
||||
}
|
||||
|
||||
/// Reloads the current URL.
|
||||
Future<void> reload() {
|
||||
return _webViewPlatformController.reload();
|
||||
}
|
||||
|
||||
/// Clears all caches used by the [WebView].
|
||||
///
|
||||
/// The following caches are cleared:
|
||||
/// 1. Browser HTTP Cache.
|
||||
/// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches.
|
||||
/// These are not yet supported in iOS WkWebView. Service workers tend to use this cache.
|
||||
/// 3. Application cache.
|
||||
/// 4. Local Storage.
|
||||
///
|
||||
/// Note: Calling this method also triggers a reload.
|
||||
Future<void> clearCache() async {
|
||||
await _webViewPlatformController.clearCache();
|
||||
return reload();
|
||||
}
|
||||
|
||||
Future<void> _updateWidget(WebView widget) async {
|
||||
_widget = widget;
|
||||
await _updateSettings(_webSettingsFromWidget(widget));
|
||||
await _updateJavascriptChannels(widget.javascriptChannels);
|
||||
}
|
||||
|
||||
Future<void> _updateSettings(WebSettings newSettings) {
|
||||
final WebSettings update =
|
||||
_clearUnchangedWebSettings(_settings, newSettings);
|
||||
_settings = newSettings;
|
||||
return _webViewPlatformController.updateSettings(update);
|
||||
}
|
||||
|
||||
Future<void> _updateJavascriptChannels(
|
||||
Set<JavascriptChannel> newChannels) async {
|
||||
final Set<String> currentChannels =
|
||||
_platformCallbacksHandler._javascriptChannels.keys.toSet();
|
||||
final Set<String> newChannelNames = _extractChannelNames(newChannels);
|
||||
final Set<String> channelsToAdd =
|
||||
newChannelNames.difference(currentChannels);
|
||||
final Set<String> channelsToRemove =
|
||||
currentChannels.difference(newChannelNames);
|
||||
if (channelsToRemove.isNotEmpty) {
|
||||
_webViewPlatformController.removeJavascriptChannels(channelsToRemove);
|
||||
}
|
||||
if (channelsToAdd.isNotEmpty) {
|
||||
_webViewPlatformController.addJavascriptChannels(channelsToAdd);
|
||||
}
|
||||
_platformCallbacksHandler._updateJavascriptChannelsFromSet(newChannels);
|
||||
}
|
||||
|
||||
/// Evaluates a JavaScript expression in the context of the current page.
|
||||
///
|
||||
/// On Android returns the evaluation result as a JSON formatted string.
|
||||
///
|
||||
/// On iOS depending on the value type the return value would be one of:
|
||||
///
|
||||
/// - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100').
|
||||
/// - For JavaScript arrays of supported types: a string formatted NSArray(e.g '(1,2,3), note that the string for NSArray is formatted and might contain newlines and extra spaces.').
|
||||
/// - Other non-primitive types are not supported on iOS and will complete the Future with an error.
|
||||
///
|
||||
/// The Future completes with an error if a JavaScript error occurred, or on iOS, if the type of the
|
||||
/// evaluated expression is not supported as described above.
|
||||
///
|
||||
/// When evaluating Javascript in a [WebView], it is best practice to wait for
|
||||
/// the [WebView.onPageFinished] callback. This guarantees all the Javascript
|
||||
/// embedded in the main frame HTML has been loaded.
|
||||
Future<String> evaluateJavascript(String javascriptString) {
|
||||
if (_settings.javascriptMode == JavascriptMode.disabled) {
|
||||
return Future<String>.error(FlutterError(
|
||||
'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.'));
|
||||
}
|
||||
if (javascriptString == null) {
|
||||
return Future<String>.error(
|
||||
ArgumentError('The argument javascriptString must not be null.'));
|
||||
}
|
||||
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
|
||||
// https://github.com/flutter/flutter/issues/26431
|
||||
// ignore: strong_mode_implicit_dynamic_method
|
||||
return _webViewPlatformController.evaluateJavascript(javascriptString);
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages cookies pertaining to all [WebView]s.
|
||||
class CookieManager {
|
||||
/// Creates a [CookieManager] -- returns the instance if it's already been called.
|
||||
factory CookieManager() {
|
||||
return _instance ??= CookieManager._();
|
||||
}
|
||||
|
||||
CookieManager._();
|
||||
|
||||
static CookieManager _instance;
|
||||
|
||||
/// Clears all cookies for all [WebView] instances.
|
||||
///
|
||||
/// This is a no op on iOS version smaller than 9.
|
||||
///
|
||||
/// Returns true if cookies were present before clearing, else false.
|
||||
Future<bool> clearCookies() => WebView.platform.clearCookies();
|
||||
}
|
||||
|
||||
// Throws an ArgumentError if `url` is not a valid URL string.
|
||||
void _validateUrlString(String url) {
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
if (uri.scheme.isEmpty) {
|
||||
throw ArgumentError('Missing scheme in URL string: "$url"');
|
||||
}
|
||||
} on FormatException catch (e) {
|
||||
throw ArgumentError(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
name: webview_flutter
|
||||
description: A Flutter plugin that provides a WebView widget on Android and iOS.
|
||||
version: 0.3.9
|
||||
author: Flutter Team <flutter-dev@googlegroups.com>
|
||||
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
|
||||
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
flutter: ">=0.11.9 <2.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
androidPackage: io.flutter.plugins.webviewflutter
|
||||
iosPrefix: FLT
|
||||
pluginClass: WebViewFlutterPlugin
|
||||