added new webview methods, added supporZoom webview option on iOS, bug fixes, prepare new version 3.4.0

This commit is contained in:
Lorenzo Pichilli 2020-06-12 04:04:41 +02:00
parent 30102a5c1a
commit 1b2de86375
23 changed files with 1081 additions and 45 deletions

View File

@ -1,3 +1,18 @@
## 3.4.0
- Added `requestFocusNodeHref`, `requestImageRef`, `getMetaTags`, `getMetaThemeColor` webview methods
- Added `WebStorage`, `LocalStorage` and `SessionStorage` class to manage `window.localStorage` and `window.sessionStorage` JavaScript [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
- Added `supportZoom` webview option also on iOS
- Fixed `zoomBy`, `setOptions` webview methods on Android
- Fixed `databaseEnabled` android webview option default value to `true`
### BREAKING CHANGES
- `evaluateJavascript` webview method now returns `null` on iOS if the evaluated JavaScript source returns `null`
- `getHtml` webview method now could return `null` if it was unable to get it.
- Moved `supportZoom` webview option to cross-platform
- `builtInZoomControls` android webview options changed default value to `true`
## 3.3.0+3
- Updated Android build.gradle version and some androidx properties

View File

@ -402,6 +402,10 @@ Screenshots:
* `getHitTestResult`: Gets the hit result for hitting an HTML elements.
* `clearFocus`: Clears the current focus. It will clear also, for example, the current text selection.
* `setContextMenu(ContextMenu contextMenu)`: Sets or updates the WebView context menu to be used next time it will appear.
* `requestFocusNodeHref`: Requests the anchor or image element URL at the last tapped point.
* `requestImageRef`: Requests the URL of the image last touched by the user.
* `getMetaTags`: Returns the list of `<meta>` tags of the current WebView.
* `getMetaThemeColor`: Returns an instance of `Color` representing the `content` value of the `<meta name="theme-color" content="">` tag of the current WebView, if available, otherwise `null`.
* `static getDefaultUserAgent`: Gets the default user agent.
##### `InAppWebViewController` Android-specific methods
@ -504,6 +508,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
* `disableVerticalScroll`: Set to `true` to disable vertical scroll. The default value is `false`.
* `disableHorizontalScroll`: Set to `true` to disable horizontal scroll. The default value is `false`.
* `disableContextMenu`: Set to `true` to disable context menu. The default value is `false`.
* `supportZoom`: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
##### `InAppWebView` Android-specific options
@ -511,9 +516,8 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
* `useOnRenderProcessGone`: Set to `true` to be able to listen at the `androidOnRenderProcessGone` event. The default value is `false`.
* `textZoom`: Sets the text zoom of the page in percent. The default value is `100`.
* `clearSessionCache`: Set to `true` to have the session cookie cache cleared before the new window is opened.
* `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`.
* `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `true`.
* `displayZoomControls`: Set to `true` if the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. The default value is `false`.
* `supportZoom`: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
* `databaseEnabled`: Set to `true` if you want the database storage API is enabled. The default value is `true`.
* `domStorageEnabled`: Set to `true` if you want the DOM storage API is enabled. The default value is `true`.
* `useWideViewPort`: Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport.
@ -622,7 +626,7 @@ Event names that starts with `android` or `ios` are events platform-specific.
* `androidOnPermissionRequest`: Event fired when the webview is requesting permission to access the specified resources and the permission currently isn't granted or denied (available only on Android).
* `androidOnGeolocationPermissionsShowPrompt`: Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin (available only on Android).
* `androidOnGeolocationPermissionsHidePrompt`: Notify the host application that a request for Geolocation permissions, made with a previous call to `androidOnGeolocationPermissionsShowPrompt` has been canceled (available only on Android).
* `androidShouldInterceptRequest`: Notify the host application of a resource request and allow the application to return the data (available only on Android).
* `androidShouldInterceptRequest`: Notify the host application of a resource request and allow the application to return the data (available only on Android). To use this event, the `useShouldInterceptRequest` option must be `true`.
* `androidOnRenderProcessGone`: Event fired when the given WebView's render process has exited (available only on Android).
* `androidOnRenderProcessResponsive`: Event called once when an unresponsive renderer currently associated with the WebView becomes responsive (available only on Android).
* `androidOnRenderProcessUnresponsive`: Event called when the renderer currently associated with the WebView becomes unresponsive as a result of a long running blocking task such as the execution of JavaScript (available only on Android).

View File

@ -5,6 +5,7 @@ import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.webkit.ValueCallback;
@ -22,7 +23,6 @@ import com.pichillilorenzo.flutter_inappwebview.Util;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
@ -365,8 +365,8 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
break;
case "zoomBy":
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float zoomFactor = (float) call.argument("zoomFactor");
webView.zoomBy(zoomFactor);
double zoomFactor = (double) call.argument("zoomFactor");
webView.zoomBy((float) zoomFactor);
result.success(true);
} else {
result.success(false);
@ -457,6 +457,20 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
result.success(false);
}
break;
case "requestFocusNodeHref":
if (webView != null) {
result.success(webView.requestFocusNodeHref());
} else {
result.success(false);
}
break;
case "requestImageRef":
if (webView != null) {
result.success(webView.requestImageRef());
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}

View File

@ -6,8 +6,10 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
@ -84,6 +86,7 @@ final public class InAppWebView extends InputAwareWebView {
public LinearLayout floatingContextMenu = null;
public Map<String, Object> contextMenu = null;
public Handler headlessHandler = new Handler(Looper.getMainLooper());
static Handler mHandler = new Handler();
public Runnable checkScrollStoppedTask;
public int initialPositionScrollStoppedTask;
@ -1184,7 +1187,7 @@ final public class InAppWebView extends InputAwareWebView {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
if (newOptionsMap.get("mixedContentMode") != null && !options.mixedContentMode.equals(newOptions.mixedContentMode))
if (newOptionsMap.get("mixedContentMode") != null && (options.mixedContentMode == null || !options.mixedContentMode.equals(newOptions.mixedContentMode)))
settings.setMixedContentMode(newOptions.mixedContentMode);
if (newOptionsMap.get("supportMultipleWindows") != null && options.supportMultipleWindows != newOptions.supportMultipleWindows)
@ -1213,7 +1216,7 @@ final public class InAppWebView extends InputAwareWebView {
if (newOptionsMap.get("cacheEnabled") != null && options.cacheEnabled != newOptions.cacheEnabled)
setCacheEnabled(newOptions.cacheEnabled);
if (newOptionsMap.get("appCachePath") != null && !options.appCachePath.equals(newOptions.appCachePath))
if (newOptionsMap.get("appCachePath") != null && (options.appCachePath == null || !options.appCachePath.equals(newOptions.appCachePath)))
settings.setAppCachePath(newOptions.appCachePath);
if (newOptionsMap.get("blockNetworkImage") != null && options.blockNetworkImage != newOptions.blockNetworkImage)
@ -1237,8 +1240,9 @@ final public class InAppWebView extends InputAwareWebView {
if (newOptionsMap.get("defaultTextEncodingName") != null && !options.defaultTextEncodingName.equals(newOptions.defaultTextEncodingName))
settings.setDefaultTextEncodingName(newOptions.defaultTextEncodingName);
if (newOptionsMap.get("disabledActionModeMenuItems") != null && !options.disabledActionModeMenuItems.equals(newOptions.disabledActionModeMenuItems))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
if (newOptionsMap.get("disabledActionModeMenuItems") != null && (options.disabledActionModeMenuItems == null ||
!options.disabledActionModeMenuItems.equals(newOptions.disabledActionModeMenuItems)))
settings.setDisabledActionModeMenuItems(newOptions.disabledActionModeMenuItems);
if (newOptionsMap.get("fantasyFontFamily") != null && !options.fantasyFontFamily.equals(newOptions.fantasyFontFamily))
@ -1318,7 +1322,8 @@ final public class InAppWebView extends InputAwareWebView {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
if (newOptionsMap.get("regexToCancelSubFramesLoading") != null && options.regexToCancelSubFramesLoading != newOptions.regexToCancelSubFramesLoading) {
if (newOptionsMap.get("regexToCancelSubFramesLoading") != null && (options.regexToCancelSubFramesLoading == null ||
!options.regexToCancelSubFramesLoading.equals(newOptions.regexToCancelSubFramesLoading))) {
if (newOptions.regexToCancelSubFramesLoading == null)
regexToCancelSubFramesLoadingCompiled = null;
else
@ -1338,13 +1343,15 @@ final public class InAppWebView extends InputAwareWebView {
if (newOptionsMap.get("scrollBarStyle") != null && !options.scrollBarStyle.equals(newOptions.scrollBarStyle))
setScrollBarStyle(newOptions.scrollBarStyle);
if (newOptionsMap.get("scrollBarDefaultDelayBeforeFade") != null && !options.scrollBarDefaultDelayBeforeFade.equals(newOptions.scrollBarDefaultDelayBeforeFade))
if (newOptionsMap.get("scrollBarDefaultDelayBeforeFade") != null && (options.scrollBarDefaultDelayBeforeFade == null ||
!options.scrollBarDefaultDelayBeforeFade.equals(newOptions.scrollBarDefaultDelayBeforeFade)))
setScrollBarDefaultDelayBeforeFade(newOptions.scrollBarDefaultDelayBeforeFade);
if (newOptionsMap.get("scrollbarFadingEnabled") != null && !options.scrollbarFadingEnabled.equals(newOptions.scrollbarFadingEnabled))
setScrollbarFadingEnabled(newOptions.scrollbarFadingEnabled);
if (newOptionsMap.get("scrollBarFadeDuration") != null && !options.scrollBarFadeDuration.equals(newOptions.scrollBarFadeDuration))
if (newOptionsMap.get("scrollBarFadeDuration") != null && (options.scrollBarFadeDuration == null ||
!options.scrollBarFadeDuration.equals(newOptions.scrollBarFadeDuration)))
setScrollBarFadeDuration(newOptions.scrollBarFadeDuration);
if (newOptionsMap.get("verticalScrollbarPosition") != null && !options.verticalScrollbarPosition.equals(newOptions.verticalScrollbarPosition))
@ -1802,6 +1809,30 @@ final public class InAppWebView extends InputAwareWebView {
});
}
public Map<String, Object> requestFocusNodeHref() {
Message msg = InAppWebView.mHandler.obtainMessage();
requestFocusNodeHref(msg);
Bundle bundle = msg.peekData();
Map<String, Object> obj = new HashMap<>();
obj.put("src", bundle.getString("src"));
obj.put("url", bundle.getString("url"));
obj.put("title", bundle.getString("title"));
return obj;
}
public Map<String, Object> requestImageRef() {
Message msg = InAppWebView.mHandler.obtainMessage();
requestImageRef(msg);
Bundle bundle = msg.peekData();
Map<String, Object> obj = new HashMap<>();
obj.put("url", bundle.getString("url"));
return obj;
}
@Override
public void dispose() {
super.dispose();

View File

@ -465,8 +465,6 @@ public class InAppWebViewClient extends WebViewClient {
}
obj.put("message", message);
Log.d(LOG_TAG, obj.toString());
channel.invokeMethod("onReceivedServerTrustAuthRequest", obj, new MethodChannel.Result() {
@Override
public void success(Object response) {

View File

@ -43,12 +43,12 @@ public class InAppWebViewOptions implements Options<InAppWebView> {
public Boolean disableVerticalScroll = false;
public Boolean disableHorizontalScroll = false;
public Boolean disableContextMenu = false;
public Boolean supportZoom = true;
public Integer textZoom = 100;
public Boolean clearSessionCache = false;
public Boolean builtInZoomControls = false;
public Boolean builtInZoomControls = true;
public Boolean displayZoomControls = false;
public Boolean supportZoom = true;
public Boolean databaseEnabled = false;
public Boolean domStorageEnabled = true;
public Boolean useWideViewPort = true;

View File

@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"android":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-03 01:35:21.255449","version":"1.17.1"}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"android":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-12 02:54:04.283438","version":"1.17.1"}

View File

@ -2,10 +2,11 @@
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "TRACK_WIDGET_CREATION=true"

View File

@ -78,8 +78,8 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView(
contextMenu: contextMenu,
initialUrl: "https://github.com/flutter",
// initialFile: "assets/index.html",
// initialUrl: "https://github.com/flutter",
initialFile: "assets/index.html",
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(

View File

@ -1 +1 @@
final environment = {"NODE_SERVER_IP":"192.168.43.166"};
final environment = {"NODE_SERVER_IP":"192.168.1.123"};

View File

@ -203,7 +203,7 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
webView!.evaluateJavascript(source: source, result: result)
}
else {
result("")
result(nil)
}
break
case "injectJavascriptFileFromUrl":
@ -405,6 +405,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
webView!.getSelectedText { (value, error) in
if let err = error {
print(err.localizedDescription)
result("")
return
}
result(value)
}
@ -418,6 +420,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
webView!.getHitTestResult { (value, error) in
if let err = error {
print(err.localizedDescription)
result(nil)
return
}
result(value)
}
@ -443,6 +447,34 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
result(false)
}
break
case "requestFocusNodeHref":
if webView != nil {
webView!.requestFocusNodeHref { (value, error) in
if let err = error {
print(err.localizedDescription)
result(nil)
return
}
result(value)
}
} else {
result(false)
}
break
case "requestImageRef":
if webView != nil {
webView!.requestImageRef { (value, error) in
if let err = error {
print(err.localizedDescription)
result(nil)
return
}
result(value)
}
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break

View File

@ -681,7 +681,6 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) {
EDIT_TEXT_TYPE: 9
};
var element = document.elementFromPoint(x, y);
console.log(element);
var data = {
type: 0,
extra: null
@ -735,6 +734,63 @@ let getSelectedTextJS = """
})();
"""
let lastTouchedAnchorOrImageJS = """
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = null;
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null;
(function() {
document.addEventListener('touchstart', function(event) {
var target = event.target;
while (target) {
if (target.tagName === 'IMG') {
var img = target;
window.flutter_inappwebview._lastImageTouched = {
src: img.src
};
var parent = img.parentNode;
while (parent) {
if (parent.tagName === 'A') {
window.flutter_inappwebview._lastAnchorOrImageTouched = {
title: parent.textContent,
url: parent.href,
src: img.src
};
break;
}
parent = parent.parentNode;
}
return;
} else if (target.tagName === 'A') {
var link = target;
var images = link.getElementsByTagName('img');
var img = (images.length > 0) ? images[0] : null;
var imgSrc = (img != null) ? img.src : null;
window.flutter_inappwebview._lastImageTouched = (img != null) ? {src: img.src} : window.flutter_inappwebview._lastImageTouched;
window.flutter_inappwebview._lastAnchorOrImageTouched = {
title: link.textContent,
url: link.href,
src: imgSrc
};
return;
}
target = target.parentNode;
}
});
})();
"""
let originalViewPortMetaTagContentJS = """
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = "";
(function() {
var metaTagNodes = document.head.getElementsByTagName('meta');
for (var i = 0; i < metaTagNodes.length; i++) {
var metaTagNode = metaTagNodes[i];
if (metaTagNode.name === "viewport") {
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content;
}
}
})();
"""
var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:]
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate {
@ -991,7 +1047,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
if (options?.enableViewportScale)! {
let originalViewPortMetaTagContentJSScript = WKUserScript(source: originalViewPortMetaTagContentJS, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(originalViewPortMetaTagContentJSScript)
if !(options?.supportZoom)! {
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); document.getElementsByTagName('head')[0].appendChild(meta);"
let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(userScript)
} else if (options?.enableViewportScale)! {
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(userScript)
@ -1018,6 +1081,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
let printJSScript = WKUserScript(source: printJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(printJSScript)
let lastTouchedAnchorOrImageJSScript = WKUserScript(source: lastTouchedAnchorOrImageJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(lastTouchedAnchorOrImageJSScript)
if (options?.useOnLoadResource)! {
let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(resourceObserverJSScript)
@ -1424,8 +1490,23 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale && newOptions.enableViewportScale {
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale {
var jscript = ""
if (newOptions.enableViewportScale) {
jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
} else {
jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); document.getElementsByTagName('head')[0].appendChild(meta);"
}
evaluateJavaScript(jscript, completionHandler: nil)
}
if newOptionsMap["supportZoom"] != nil && options?.supportZoom != newOptions.supportZoom {
var jscript = ""
if (newOptions.supportZoom) {
jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); document.getElementsByTagName('head')[0].appendChild(meta);"
} else {
jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); document.getElementsByTagName('head')[0].appendChild(meta);"
}
evaluateJavaScript(jscript, completionHandler: nil)
}
@ -1635,7 +1716,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
if value == nil {
result!("")
result!(nil)
return
}
@ -2742,6 +2823,34 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
public func requestFocusNodeHref(completionHandler: @escaping ([String: Any?]?, Error?) -> Void) {
if configuration.preferences.javaScriptEnabled {
// add some delay to make it sure _lastAnchorOrImageTouched is updated
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched", completionHandler: {(value, error) in
let lastAnchorOrImageTouched = value as? [String: Any?]
completionHandler(lastAnchorOrImageTouched, error)
})
}
} else {
completionHandler(nil, nil)
}
}
public func requestImageRef(completionHandler: @escaping ([String: Any?]?, Error?) -> Void) {
if configuration.preferences.javaScriptEnabled {
// add some delay to make it sure _lastImageTouched is updated
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched", completionHandler: {(value, error) in
let lastImageTouched = value as? [String: Any?]
completionHandler(lastImageTouched, error)
})
}
} else {
completionHandler(nil, nil)
}
}
public func clearFocus() {
self.scrollView.subviews.first?.resignFirstResponder()
}

View File

@ -34,6 +34,7 @@ public class InAppWebViewOptions: Options<InAppWebView> {
var disableVerticalScroll = false
var disableHorizontalScroll = false
var disableContextMenu = false
var supportZoom = true
var disallowOverScroll = false
var enableViewportScale = false

View File

@ -36,3 +36,4 @@ export 'src/content_blocker.dart';
export 'src/http_auth_credentials_database.dart';
export 'src/web_storage_manager.dart';
export 'src/context_menu.dart';
export 'src/web_storage.dart';

View File

@ -41,7 +41,7 @@ class CookieManager {
int expiresDate,
int maxAge,
bool isSecure}) async {
if (domain == null) domain = _getDomainName(url);
if (domain == null || domain.isEmpty) domain = _getDomainName(url);
assert(url != null && url.isNotEmpty);
assert(name != null && name.isNotEmpty);

View File

@ -13,7 +13,7 @@ import 'in_app_webview_controller.dart';
///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.
class InAppWebView extends StatefulWidget implements WebView {
/// `gestureRecognizers` specifies which gestures should be consumed by the web view.
/// `gestureRecognizers` specifies which gestures should be consumed by the WebView.
/// 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

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';
import 'dart:convert';
import 'dart:core';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -18,6 +19,8 @@ import 'webview_options.dart';
import 'headless_in_app_webview.dart';
import 'webview.dart';
import 'in_app_webview.dart';
import 'web_storage.dart';
import 'util.dart';
///List of forbidden names for JavaScript handlers.
const javaScriptHandlerForbiddenNames = [
@ -56,6 +59,8 @@ class InAppWebViewController {
///iOS controller that contains only ios-specific methods
IOSInAppWebViewController ios;
WebStorage webStorage;
InAppWebViewController(dynamic id, WebView webview) {
this._id = id;
this._channel =
@ -64,6 +69,7 @@ class InAppWebViewController {
this._webview = webview;
this.android = AndroidInAppWebViewController(this);
this.ios = IOSInAppWebViewController(this);
this.webStorage = WebStorage(localStorage: LocalStorage(this), sessionStorage: SessionStorage(this));
}
InAppWebViewController.fromInAppBrowser(
@ -789,8 +795,11 @@ class InAppWebViewController {
///If this doesn't work, it tries to get the content reading the file:
///- checking if it is an asset (`file:///`) or
///- downloading it using an `HttpClient` through the WebView's current url.
///
///Returns `null` if it was unable to get it.
Future<String> getHtml() async {
var html = "";
String html;
InAppWebViewGroupOptions options = await getOptions();
if (options != null && options.crossPlatform.javaScriptEnabled == true) {
html = await evaluateJavascript(
@ -830,7 +839,7 @@ class InAppWebViewController {
String manifestUrl;
var html = await getHtml();
if (html.isEmpty) {
if (html != null && html.isEmpty) {
return favicons;
}
@ -1503,6 +1512,121 @@ class InAppWebViewController {
_inAppBrowser?.contextMenu = contextMenu;
}
///Requests the anchor or image element URL at the last tapped point.
///
///**NOTE**: On iOS it is implemented using JavaScript.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#requestFocusNodeHref(android.os.Message)
Future<RequestFocusNodeHrefResult> requestFocusNodeHref() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic> result = await _channel.invokeMethod('requestFocusNodeHref', args);
return result != null ? RequestFocusNodeHrefResult(
url: result['url'],
title: result['title'],
src: result['src'],
) : null;
}
///Requests the URL of the image last touched by the user.
///
///**NOTE**: On iOS it is implemented using JavaScript.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#requestImageRef(android.os.Message)
Future<RequestImageRefResult> requestImageRef() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic> result = await _channel.invokeMethod('requestImageRef', args);
return result != null ? RequestImageRefResult(
url: result['url'],
) : null;
}
///Returns the list of `<meta>` tags of the current WebView.
///
///**NOTE**: It is implemented using JavaScript.
Future<List<MetaTag>> getMetaTags() async {
List<MetaTag> metaTags = [];
List<Map<dynamic, dynamic>> metaTagList = (await evaluateJavascript(source: """
(function() {
var metaTags = [];
var metaTagNodes = document.head.getElementsByTagName('meta');
for (var i = 0; i < metaTagNodes.length; i++) {
var metaTagNode = metaTagNodes[i];
var otherAttributes = metaTagNode.getAttributeNames();
var nameIndex = otherAttributes.indexOf("name");
if (nameIndex !== -1) otherAttributes.splice(nameIndex, 1);
var contentIndex = otherAttributes.indexOf("content");
if (contentIndex !== -1) otherAttributes.splice(contentIndex, 1);
var attrs = [];
for (var j = 0; j < otherAttributes.length; j++) {
var otherAttribute = otherAttributes[j];
attrs.push(
{
name: otherAttribute,
value: metaTagNode.getAttribute(otherAttribute)
}
);
}
metaTags.push(
{
name: metaTagNode.name,
content: metaTagNode.content,
attrs: attrs
}
);
}
return metaTags;
})();
"""))?.cast<Map<dynamic, dynamic>>();
if (metaTagList == null) {
return metaTags;
}
for (var metaTag in metaTagList) {
var attrs = <MetaTagAttribute>[];
for (var metaTagAttr in metaTag["attrs"]) {
attrs.add(
MetaTagAttribute(name: metaTagAttr["name"], value: metaTagAttr["value"])
);
}
metaTags.add(
MetaTag(name: metaTag["name"], content: metaTag["content"], attrs: attrs)
);
}
return metaTags;
}
///Returns an instance of [Color] representing the `content` value of the
///`<meta name="theme-color" content="">` tag of the current WebView, if available, otherwise `null`.
///
///**NOTE**: It is implemented using JavaScript.
Future<Color> getMetaThemeColor() async {
var metaTags = await getMetaTags();
MetaTag metaTagThemeColor;
for (var metaTag in metaTags) {
if (metaTag.name == "theme-color") {
metaTagThemeColor = metaTag;
break;
}
}
if (metaTagThemeColor == null) {
return null;
}
var colorValue = metaTagThemeColor.content;
return Util.convertColorFromStringRepresentation(colorValue);
}
///Gets the default user agent.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context)
@ -1517,6 +1641,7 @@ class AndroidInAppWebViewController {
InAppWebViewController _controller;
AndroidInAppWebViewController(InAppWebViewController controller) {
assert(controller != null);
this._controller = controller;
}
@ -1719,6 +1844,7 @@ class IOSInAppWebViewController {
InAppWebViewController _controller;
IOSInAppWebViewController(InAppWebViewController controller) {
assert(controller != null);
this._controller = controller;
}

View File

@ -3279,3 +3279,146 @@ class AndroidWebViewPackageInfo {
return toMap().toString();
}
}
///Class that represents the result used by the [InAppWebViewController.requestFocusNodeHref] method.
class RequestFocusNodeHrefResult {
///The anchor's href attribute.
String url;
///The anchor's text.
String title;
///The image's src attribute.
String src;
RequestFocusNodeHrefResult(
{this.url,
this.title,
this.src});
Map<String, dynamic> toMap() {
return {
"url": url,
"title": title,
"src": src
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents the result used by the [InAppWebViewController.requestImageRef] method.
class RequestImageRefResult {
///The image's url.
String url;
RequestImageRefResult({this.url});
Map<String, dynamic> toMap() {
return {
"url": url,
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents a `<meta>` HTML tag. It is used by the [InAppWebViewController.getMetaTags] method.
class MetaTag {
///The meta tag name value.
String name;
///The meta tag content value.
String content;
///The meta tag attributes list.
List<MetaTagAttribute> attrs;
MetaTag({this.name, this.content, this.attrs});
Map<String, dynamic> toMap() {
return {
"name": name,
"content": content,
"attrs": attrs
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents an attribute of a `<meta>` HTML tag. It is used by the [MetaTag] class.
class MetaTagAttribute {
///The attribute name.
String name;
///The attribute value.
String value;
MetaTagAttribute({this.name, this.value});
Map<String, dynamic> toMap() {
return {
"name": name,
"value": value,
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
class WebStorageType {
final String _value;
const WebStorageType._internal(this._value);
static WebStorageType fromValue(String value) {
return ([
"localStorage",
"sessionStorage",
].contains(value))
? WebStorageType._internal(value)
: null;
}
String toValue() => _value;
@override
String toString() => _value;
static const LOCAL_STORAGE =
const WebStorageType._internal("localStorage");
static const SESSION_STORAGE =
const WebStorageType._internal("sessionStorage");
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}

425
lib/src/util.dart Normal file
View File

@ -0,0 +1,425 @@
import 'dart:math';
import 'package:flutter/material.dart';
class Util {
static Color convertColorFromStringRepresentation(String colorValue) {
if (colorValue.startsWith("#")) {
return Util.getColorFromHex(colorValue);
} else if (colorValue.startsWith("rgb(")) {
return Util.getColorFromRgbString(colorValue);
} else if (colorValue.startsWith("rgba(")) {
return Util.getColorFromRgbaString(colorValue);
} else if (colorValue.startsWith("hls(")) {
return Util.getColorFromHlsString(colorValue);
} else if (colorValue.startsWith("hlsa(")) {
return Util.getColorFromHlsaString(colorValue);
} else {
/**
This part of the code is generated using the JavaScript code below on this link: https://drafts.csswg.org/css-color/#typedef-color
let code = 'switch(colorValue) {\n';
const table = document.querySelector('.named-color-table');
const colorNameCells = table.querySelectorAll('tr > th dfn');
const colorHexValueCells = table.querySelectorAll('tr > td:nth-child(4)');
for (let i = 0; i < colorNameCells.length; i++) {
const colorName = colorNameCells[i].textContent.trim();
const colorHexValue = colorHexValueCells[i].textContent.trim();
code += ' case "' + colorName + '":\n';
code += ' return Util.getColorFromHex("' + colorHexValue + '");\n';
}
code += '}';
*/
switch(colorValue) {
case "aliceblue":
return Util.getColorFromHex("#f0f8ff");
case "antiquewhite":
return Util.getColorFromHex("#faebd7");
case "aqua":
return Util.getColorFromHex("#00ffff");
case "aquamarine":
return Util.getColorFromHex("#7fffd4");
case "azure":
return Util.getColorFromHex("#f0ffff");
case "beige":
return Util.getColorFromHex("#f5f5dc");
case "bisque":
return Util.getColorFromHex("#ffe4c4");
case "black":
return Util.getColorFromHex("#000000");
case "blanchedalmond":
return Util.getColorFromHex("#ffebcd");
case "blue":
return Util.getColorFromHex("#0000ff");
case "blueviolet":
return Util.getColorFromHex("#8a2be2");
case "brown":
return Util.getColorFromHex("#a52a2a");
case "burlywood":
return Util.getColorFromHex("#deb887");
case "cadetblue":
return Util.getColorFromHex("#5f9ea0");
case "chartreuse":
return Util.getColorFromHex("#7fff00");
case "chocolate":
return Util.getColorFromHex("#d2691e");
case "coral":
return Util.getColorFromHex("#ff7f50");
case "cornflowerblue":
return Util.getColorFromHex("#6495ed");
case "cornsilk":
return Util.getColorFromHex("#fff8dc");
case "crimson":
return Util.getColorFromHex("#dc143c");
case "cyan":
return Util.getColorFromHex("#00ffff");
case "darkblue":
return Util.getColorFromHex("#00008b");
case "darkcyan":
return Util.getColorFromHex("#008b8b");
case "darkgoldenrod":
return Util.getColorFromHex("#b8860b");
case "darkgray":
return Util.getColorFromHex("#a9a9a9");
case "darkgreen":
return Util.getColorFromHex("#006400");
case "darkgrey":
return Util.getColorFromHex("#a9a9a9");
case "darkkhaki":
return Util.getColorFromHex("#bdb76b");
case "darkmagenta":
return Util.getColorFromHex("#8b008b");
case "darkolivegreen":
return Util.getColorFromHex("#556b2f");
case "darkorange":
return Util.getColorFromHex("#ff8c00");
case "darkorchid":
return Util.getColorFromHex("#9932cc");
case "darkred":
return Util.getColorFromHex("#8b0000");
case "darksalmon":
return Util.getColorFromHex("#e9967a");
case "darkseagreen":
return Util.getColorFromHex("#8fbc8f");
case "darkslateblue":
return Util.getColorFromHex("#483d8b");
case "darkslategray":
return Util.getColorFromHex("#2f4f4f");
case "darkslategrey":
return Util.getColorFromHex("#2f4f4f");
case "darkturquoise":
return Util.getColorFromHex("#00ced1");
case "darkviolet":
return Util.getColorFromHex("#9400d3");
case "deeppink":
return Util.getColorFromHex("#ff1493");
case "deepskyblue":
return Util.getColorFromHex("#00bfff");
case "dimgray":
return Util.getColorFromHex("#696969");
case "dimgrey":
return Util.getColorFromHex("#696969");
case "dodgerblue":
return Util.getColorFromHex("#1e90ff");
case "firebrick":
return Util.getColorFromHex("#b22222");
case "floralwhite":
return Util.getColorFromHex("#fffaf0");
case "forestgreen":
return Util.getColorFromHex("#228b22");
case "fuchsia":
return Util.getColorFromHex("#ff00ff");
case "gainsboro":
return Util.getColorFromHex("#dcdcdc");
case "ghostwhite":
return Util.getColorFromHex("#f8f8ff");
case "gold":
return Util.getColorFromHex("#ffd700");
case "goldenrod":
return Util.getColorFromHex("#daa520");
case "gray":
return Util.getColorFromHex("#808080");
case "green":
return Util.getColorFromHex("#008000");
case "greenyellow":
return Util.getColorFromHex("#adff2f");
case "grey":
return Util.getColorFromHex("#808080");
case "honeydew":
return Util.getColorFromHex("#f0fff0");
case "hotpink":
return Util.getColorFromHex("#ff69b4");
case "indianred":
return Util.getColorFromHex("#cd5c5c");
case "indigo":
return Util.getColorFromHex("#4b0082");
case "ivory":
return Util.getColorFromHex("#fffff0");
case "khaki":
return Util.getColorFromHex("#f0e68c");
case "lavender":
return Util.getColorFromHex("#e6e6fa");
case "lavenderblush":
return Util.getColorFromHex("#fff0f5");
case "lawngreen":
return Util.getColorFromHex("#7cfc00");
case "lemonchiffon":
return Util.getColorFromHex("#fffacd");
case "lightblue":
return Util.getColorFromHex("#add8e6");
case "lightcoral":
return Util.getColorFromHex("#f08080");
case "lightcyan":
return Util.getColorFromHex("#e0ffff");
case "lightgoldenrodyellow":
return Util.getColorFromHex("#fafad2");
case "lightgray":
return Util.getColorFromHex("#d3d3d3");
case "lightgreen":
return Util.getColorFromHex("#90ee90");
case "lightgrey":
return Util.getColorFromHex("#d3d3d3");
case "lightpink":
return Util.getColorFromHex("#ffb6c1");
case "lightsalmon":
return Util.getColorFromHex("#ffa07a");
case "lightseagreen":
return Util.getColorFromHex("#20b2aa");
case "lightskyblue":
return Util.getColorFromHex("#87cefa");
case "lightslategray":
return Util.getColorFromHex("#778899");
case "lightslategrey":
return Util.getColorFromHex("#778899");
case "lightsteelblue":
return Util.getColorFromHex("#b0c4de");
case "lightyellow":
return Util.getColorFromHex("#ffffe0");
case "lime":
return Util.getColorFromHex("#00ff00");
case "limegreen":
return Util.getColorFromHex("#32cd32");
case "linen":
return Util.getColorFromHex("#faf0e6");
case "magenta":
return Util.getColorFromHex("#ff00ff");
case "maroon":
return Util.getColorFromHex("#800000");
case "mediumaquamarine":
return Util.getColorFromHex("#66cdaa");
case "mediumblue":
return Util.getColorFromHex("#0000cd");
case "mediumorchid":
return Util.getColorFromHex("#ba55d3");
case "mediumpurple":
return Util.getColorFromHex("#9370db");
case "mediumseagreen":
return Util.getColorFromHex("#3cb371");
case "mediumslateblue":
return Util.getColorFromHex("#7b68ee");
case "mediumspringgreen":
return Util.getColorFromHex("#00fa9a");
case "mediumturquoise":
return Util.getColorFromHex("#48d1cc");
case "mediumvioletred":
return Util.getColorFromHex("#c71585");
case "midnightblue":
return Util.getColorFromHex("#191970");
case "mintcream":
return Util.getColorFromHex("#f5fffa");
case "mistyrose":
return Util.getColorFromHex("#ffe4e1");
case "moccasin":
return Util.getColorFromHex("#ffe4b5");
case "navajowhite":
return Util.getColorFromHex("#ffdead");
case "navy":
return Util.getColorFromHex("#000080");
case "oldlace":
return Util.getColorFromHex("#fdf5e6");
case "olive":
return Util.getColorFromHex("#808000");
case "olivedrab":
return Util.getColorFromHex("#6b8e23");
case "orange":
return Util.getColorFromHex("#ffa500");
case "orangered":
return Util.getColorFromHex("#ff4500");
case "orchid":
return Util.getColorFromHex("#da70d6");
case "palegoldenrod":
return Util.getColorFromHex("#eee8aa");
case "palegreen":
return Util.getColorFromHex("#98fb98");
case "paleturquoise":
return Util.getColorFromHex("#afeeee");
case "palevioletred":
return Util.getColorFromHex("#db7093");
case "papayawhip":
return Util.getColorFromHex("#ffefd5");
case "peachpuff":
return Util.getColorFromHex("#ffdab9");
case "peru":
return Util.getColorFromHex("#cd853f");
case "pink":
return Util.getColorFromHex("#ffc0cb");
case "plum":
return Util.getColorFromHex("#dda0dd");
case "powderblue":
return Util.getColorFromHex("#b0e0e6");
case "purple":
return Util.getColorFromHex("#800080");
case "rebeccapurple":
return Util.getColorFromHex("#663399");
case "red":
return Util.getColorFromHex("#ff0000");
case "rosybrown":
return Util.getColorFromHex("#bc8f8f");
case "royalblue":
return Util.getColorFromHex("#4169e1");
case "saddlebrown":
return Util.getColorFromHex("#8b4513");
case "salmon":
return Util.getColorFromHex("#fa8072");
case "sandybrown":
return Util.getColorFromHex("#f4a460");
case "seagreen":
return Util.getColorFromHex("#2e8b57");
case "seashell":
return Util.getColorFromHex("#fff5ee");
case "sienna":
return Util.getColorFromHex("#a0522d");
case "silver":
return Util.getColorFromHex("#c0c0c0");
case "skyblue":
return Util.getColorFromHex("#87ceeb");
case "slateblue":
return Util.getColorFromHex("#6a5acd");
case "slategray":
return Util.getColorFromHex("#708090");
case "slategrey":
return Util.getColorFromHex("#708090");
case "snow":
return Util.getColorFromHex("#fffafa");
case "springgreen":
return Util.getColorFromHex("#00ff7f");
case "steelblue":
return Util.getColorFromHex("#4682b4");
case "tan":
return Util.getColorFromHex("#d2b48c");
case "teal":
return Util.getColorFromHex("#008080");
case "thistle":
return Util.getColorFromHex("#d8bfd8");
case "tomato":
return Util.getColorFromHex("#ff6347");
case "turquoise":
return Util.getColorFromHex("#40e0d0");
case "violet":
return Util.getColorFromHex("#ee82ee");
case "wheat":
return Util.getColorFromHex("#f5deb3");
case "white":
return Util.getColorFromHex("#ffffff");
case "whitesmoke":
return Util.getColorFromHex("#f5f5f5");
case "yellow":
return Util.getColorFromHex("#ffff00");
case "yellowgreen":
return Util.getColorFromHex("#9acd32");
}
}
return null;
}
static Color getColorFromHex(String hexString) {
hexString = hexString.trim();
if (hexString.length == 4) { // convert for example #f00 to #ff0000
hexString = "#" + (hexString[1] * 2) + (hexString[2] * 2) + (hexString[3] * 2);
}
final buffer = StringBuffer();
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
buffer.write(hexString.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
}
static Color getColorFromRgbString(String rgbString) {
rgbString = rgbString.trim();
var rgbValues = rgbString
.substring(4, rgbString.length - 1)
.split(",")
.map((rbgValue) => int.parse(rbgValue.trim()))
.toList();
return Color.fromRGBO(rgbValues[0], rgbValues[1], rgbValues[2], 1);
}
static Color getColorFromRgbaString(String rgbaString) {
rgbaString = rgbaString.trim();
var rgbaValues = rgbaString
.substring(5, rgbaString.length - 1)
.split(",")
.map((rbgValue) => rbgValue.trim())
.toList();
return Color.fromRGBO(int.parse(rgbaValues[0]), int.parse(rgbaValues[1]), int.parse(rgbaValues[2]), double.parse(rgbaValues[3]));
}
static Color getColorFromHlsString(String hlsString) {
hlsString = hlsString.trim();
var hlsValues = hlsString
.substring(4, hlsString.length - 1)
.split(",")
.map((rbgValue) => double.parse(rbgValue.trim()))
.toList();
var rgbValues = hslToRgb(hlsValues[0], hlsValues[1], hlsValues[2]);
return Color.fromRGBO(rgbValues[0], rgbValues[1], rgbValues[2], 1);
}
static Color getColorFromHlsaString(String hlsaString) {
hlsaString = hlsaString.trim();
var hlsaValues = hlsaString
.substring(5, hlsaString.length - 1)
.split(",")
.map((rbgValue) => double.parse(rbgValue.trim()))
.toList();
var rgbaValues = hslToRgb(hlsaValues[0], hlsaValues[1], hlsaValues[2]);
return Color.fromRGBO(rgbaValues[0], rgbaValues[1], rgbaValues[2], hlsaValues[3]);
}
static List<num> hslToRgb(double h, double s, double l){
double r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
double p = 2 * l - q;
r = hueToRgb(p, q, h + 1/3);
g = hueToRgb(p, q, h);
b = hueToRgb(p, q, h - 1/3);
}
var rgb = [to255(r), to255(g), to255(b)];
return rgb;
}
static num to255(double v) {
return min(255, 256*v);
}
/// Helper method that converts hue to rgb
static double hueToRgb(double p, double q, double t) {
if (t < 0)
t += 1;
if (t > 1)
t -= 1;
if (t < 1/6)
return p + (q - p) * 6 * t;
if (t < 1/2)
return q;
if (t < 2/3)
return p + (q - p) * (2/3 - t) * 6;
return p;
}
}

136
lib/src/web_storage.dart Normal file
View File

@ -0,0 +1,136 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'in_app_webview_controller.dart';
import 'types.dart';
class WebStorage {
LocalStorage localStorage;
SessionStorage sessionStorage;
WebStorage({@required this.localStorage, @required this.sessionStorage});
}
class WebStorageItem {
String key;
dynamic value;
WebStorageItem({this.key, this.value});
Map<String, dynamic> toMap() {
return {
"key": key,
"value": value,
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
class Storage {
InAppWebViewController _controller;
WebStorageType webStorageType;
Storage(InAppWebViewController controller, this.webStorageType) {
assert(controller != null && this.webStorageType != null);
this._controller = controller;
}
Future<int> length() async {
var result = await _controller.evaluateJavascript(source: """
window.$webStorageType.length;
""");
return result != null ? int.parse(json.decode(result)) : null;
}
Future<void> setItem({@required String key, @required dynamic value}) async {
var encodedValue = json.encode(value);
await _controller.evaluateJavascript(source: """
window.$webStorageType.setItem("$key", ${ value is String ? encodedValue : "JSON.stringify($encodedValue)" });
""");
}
Future<dynamic> getItem({@required String key}) async {
var itemValue = await _controller.evaluateJavascript(source: """
window.$webStorageType.getItem("$key");
""");
if (itemValue == null) {
return null;
}
try {
return json.decode(itemValue);
} catch (e) {}
return itemValue;
}
Future<void> removeItem({@required String key}) async {
await _controller.evaluateJavascript(source: """
window.$webStorageType.removeItem("$key");
""");
}
Future<List<WebStorageItem>> getItems() async {
var webStorageItems = <WebStorageItem>[];
List<Map<dynamic, dynamic>> items = (await _controller.evaluateJavascript(source: """
(function() {
var webStorageItems = [];
for(var i = 0; i < window.$webStorageType.length; i++){
var key = window.$webStorageType.key(i);
webStorageItems.push(
{
key: key,
value: window.$webStorageType.getItem(key)
}
);
}
return webStorageItems;
})();
""")).cast<Map<dynamic, dynamic>>();
if (items == null) {
return webStorageItems;
}
for (var item in items) {
webStorageItems.add(
WebStorageItem(key: item["key"], value: item["value"])
);
}
return webStorageItems;
}
Future<void> clear() async {
await _controller.evaluateJavascript(source: """
window.$webStorageType.clear();
""");
}
Future<String> key({@required int index}) async {
var result = await _controller.evaluateJavascript(source: """
window.$webStorageType.key($index);
""");
return result != null ? json.decode(result) : null;
}
}
class LocalStorage extends Storage {
LocalStorage(InAppWebViewController controller) : super(controller, WebStorageType.LOCAL_STORAGE);
}
class SessionStorage extends Storage {
SessionStorage(InAppWebViewController controller) : super(controller, WebStorageType.SESSION_STORAGE);
}

View File

@ -380,7 +380,7 @@ abstract class WebView {
///
///[request] Object containing the details of the request.
///
///**NOTE**: available only on Android.
///**NOTE**: available only on Android. In order to be able to listen this event, you need to set [AndroidInAppWebViewOptions.useShouldInterceptRequest] option to `true`.
///
///**Official Android API**:
///- https://developer.android.com/reference/android/webkit/WebViewClient#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest)

View File

@ -159,6 +159,9 @@ class InAppWebViewOptions
///Set to `true` to disable context menu. The default value is `false`.
bool disableContextMenu;
///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
bool supportZoom;
InAppWebViewOptions(
{this.useShouldOverrideUrlLoading = false,
this.useOnLoadResource = false,
@ -183,7 +186,8 @@ class InAppWebViewOptions
this.transparentBackground = false,
this.disableVerticalScroll = false,
this.disableHorizontalScroll = false,
this.disableContextMenu = false}) {
this.disableContextMenu = false,
this.supportZoom = true}) {
if (this.minimumFontSize == null)
this.minimumFontSize = Platform.isAndroid ? 8 : 0;
assert(!this.resourceCustomSchemes.contains("http") &&
@ -221,7 +225,8 @@ class InAppWebViewOptions
"transparentBackground": transparentBackground,
"disableVerticalScroll": disableVerticalScroll,
"disableHorizontalScroll": disableHorizontalScroll,
"disableContextMenu": disableContextMenu
"disableContextMenu": disableContextMenu,
"supportZoom": supportZoom
};
}
@ -266,6 +271,7 @@ class InAppWebViewOptions
options.disableVerticalScroll = map["disableVerticalScroll"];
options.disableHorizontalScroll = map["disableHorizontalScroll"];
options.disableContextMenu = map["disableContextMenu"];
options.supportZoom = map["supportZoom"];
return options;
}
@ -289,15 +295,12 @@ class AndroidInAppWebViewOptions
///Set to `true` to have the session cookie cache cleared before the new window is opened.
bool clearSessionCache;
///Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`.
///Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `true`.
bool builtInZoomControls;
///Set to `true` if the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. The default value is `false`.
bool displayZoomControls;
///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
bool supportZoom;
///Set to `true` if you want the database storage API is enabled. The default value is `true`.
bool databaseEnabled;
@ -493,10 +496,9 @@ class AndroidInAppWebViewOptions
AndroidInAppWebViewOptions({
this.textZoom = 100,
this.clearSessionCache = false,
this.builtInZoomControls = false,
this.builtInZoomControls = true,
this.displayZoomControls = false,
this.supportZoom = true,
this.databaseEnabled = false,
this.databaseEnabled = true,
this.domStorageEnabled = true,
this.useWideViewPort = true,
this.safeBrowsingEnabled = true,
@ -553,7 +555,6 @@ class AndroidInAppWebViewOptions
"clearSessionCache": clearSessionCache,
"builtInZoomControls": builtInZoomControls,
"displayZoomControls": displayZoomControls,
"supportZoom": supportZoom,
"databaseEnabled": databaseEnabled,
"domStorageEnabled": domStorageEnabled,
"useWideViewPort": useWideViewPort,
@ -610,7 +611,6 @@ class AndroidInAppWebViewOptions
options.clearSessionCache = map["clearSessionCache"];
options.builtInZoomControls = map["builtInZoomControls"];
options.displayZoomControls = map["displayZoomControls"];
options.supportZoom = map["supportZoom"];
options.databaseEnabled = map["databaseEnabled"];
options.domStorageEnabled = map["domStorageEnabled"];
options.useWideViewPort = map["useWideViewPort"];

View File

@ -1,6 +1,6 @@
name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
version: 3.3.0+3
version: 3.4.0
homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment: