From 3f1ed3ba70ef30cd51dba513859e1650963fe9c2 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Mon, 8 Feb 2021 01:17:12 +0100 Subject: [PATCH] added ScreenshotConfiguration class and screenshotConfiguration optional argument to takeScreenshot WebView method --- CHANGELOG.md | 6 +- .../InAppWebView/InAppWebView.java | 54 ++++- .../InAppWebViewMethodHandler.java | 6 +- example/.flutter-plugins-dependencies | 2 +- example/ios/Flutter/Flutter.podspec | 18 -- .../ios/Flutter/flutter_export_environment.sh | 7 +- example/lib/in_app_webiew_example.screen.dart | 5 + ios/Classes/InAppWebView.swift | 34 ++- ios/Classes/InAppWebViewMethodHandler.swift | 3 +- lib/src/in_app_webview_controller.dart | 13 +- lib/src/types.dart | 229 ++++++++++++++++-- 11 files changed, 308 insertions(+), 69 deletions(-) delete mode 100644 example/ios/Flutter/Flutter.podspec diff --git a/CHANGELOG.md b/CHANGELOG.md index 730385b0..4821b4e6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,14 @@ - Added `allowUniversalAccessFromFileURLs` and `allowFileAccessFromFileURLs` WebView options also for iOS (also thanks to [liranhao](https://github.com/liranhao)) - Added limited cookies support on iOS below 11.0 using JavaScript - Added `IOSCookieManager` class and `CookieManager.instance().ios.getAllCookies` iOS-specific method -- Added `UserScript`, `UserScriptInjectionTime`, `ContentWorld`, `AndroidWebViewFeature`, `AndroidServiceWorkerController`, `AndroidServiceWorkerClient` classes +- Added `UserScript`, `UserScriptInjectionTime`, `ContentWorld`, `AndroidWebViewFeature`, `AndroidServiceWorkerController`, `AndroidServiceWorkerClient`, `ScreenshotConfiguration` classes - Added `initialUserScripts` WebView option - Added `addUserScript`, `addUserScripts`, `removeUserScript`, `removeUserScripts`, `removeAllUserScripts`, `callAsyncJavaScript` WebView methods - Added `contentWorld` argument to `evaluateJavascript` WebView method - Added `isDirectionalLockEnabled`, `mediaType`, `pageZoom`, `limitsNavigationsToAppBoundDomains` iOS-specific webview options - Added `handlesURLScheme` iOS-specific webview method +- Added `iosAnimated` optional argument to `zoomBy` WebView method +- Added `screenshotConfiguration` optional argument to `takeScreenshot` WebView method - Updated integration tests - Merge "Upgraded appcompat to 1.2.0-rc-02" [#465](https://github.com/pichillilorenzo/flutter_inappwebview/pull/465) (thanks to [andreidiaconu](https://github.com/andreidiaconu)) - Merge "Added missing field 'headers' which returned by WebResourceResponse.toMap()" [#490](https://github.com/pichillilorenzo/flutter_inappwebview/pull/490) (thanks to [Doflatango](https://github.com/Doflatango)) @@ -41,7 +43,7 @@ - Removed `debuggingEnabled` WebView option; on Android you should use now the `AndroidInAppWebViewController.setWebContentsDebuggingEnabled(bool debuggingEnabled)` static method; on iOS, debugging is always enabled - `allowUniversalAccessFromFileURLs` and `allowFileAccessFromFileURLs` WebView options moved from Android-specific options to cross-platform options - Added `callAsyncJavaScript` name to the list of javaScriptHandlerForbiddenNames -- Added `iosAnimated` optional argument to `zoomBy` WebView method +- Changed `zoomBy` WebView method signature ## 4.0.0+4 diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java index a07ff4c9..0ff3a5c1 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java @@ -1132,7 +1132,7 @@ final public class InAppWebView extends InputAwareWebView { WebStorage.getInstance().deleteAllData(); } - public void takeScreenshot(final MethodChannel.Result result) { + public void takeScreenshot(final Map screenshotConfiguration, final MethodChannel.Result result) { headlessHandler.post(new Runnable() { @Override public void run() { @@ -1156,29 +1156,59 @@ final public class InAppWebView extends InputAwareWebView { scrollOffset = 0; } - Bitmap resized = Bitmap.createBitmap( - b, 0, scrollOffset, b.getWidth(), measuredHeight); + int rectX = 0; + int rectY = scrollOffset; + int rectWidth = b.getWidth(); + int rectHeight = measuredHeight; + + Bitmap resized = Bitmap.createBitmap(b, rectX, rectY, rectWidth, rectHeight); + + Map rect = (Map) screenshotConfiguration.get("rect"); + if (rect != null) { + rectX = (int) Math.floor(rect.get("x") * scale + 0.5); + rectY = (int) Math.floor(rect.get("y") * scale + 0.5); + rectWidth = Math.min(resized.getWidth(), (int) Math.floor(rect.get("width") * scale + 0.5)); + rectHeight = Math.min(resized.getHeight(), (int) Math.floor(rect.get("height") * scale + 0.5)); + resized = Bitmap.createBitmap( + b, + rectX, + rectY, + rectWidth, + rectHeight); + } + + Double snapshotWidth = (Double) screenshotConfiguration.get("snapshotWidth"); + if (snapshotWidth != null) { + int dstWidth = (int) Math.floor(snapshotWidth * scale + 0.5); + float ratioBitmap = (float) resized.getWidth() / (float) resized.getHeight(); + int dstHeight = (int) ((float) dstWidth / ratioBitmap); + resized = Bitmap.createScaledBitmap(resized, dstWidth, dstHeight, true); + } ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - resized.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); + Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.PNG; + try { + compressFormat = Bitmap.CompressFormat.valueOf((String) screenshotConfiguration.get("compressFormat")); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + + resized.compress( + compressFormat, + (Integer) screenshotConfiguration.get("quality"), + byteArrayOutputStream); + try { byteArrayOutputStream.close(); } catch (IOException e) { e.printStackTrace(); - String errorMessage = e.getMessage(); - if (errorMessage != null) { - Log.e(LOG_TAG, errorMessage); - } } resized.recycle(); result.success(byteArrayOutputStream.toByteArray()); } catch (IllegalArgumentException e) { - String errorMessage = e.getMessage(); - if (errorMessage != null) { - Log.e(LOG_TAG, errorMessage); - } + e.printStackTrace(); result.success(null); } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java index 68fcbb01..9fa5d55a 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java @@ -140,8 +140,10 @@ public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandle result.success((webView != null) && webView.isLoading()); break; case "takeScreenshot": - if (webView != null) - webView.takeScreenshot(result); + if (webView != null) { + Map screenshotConfiguration = (Map) call.argument("screenshotConfiguration"); + webView.takeScreenshot(screenshotConfiguration, result); + } else result.success(null); break; diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index c2cad3d3..58ebbeae 100644 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"android":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":["device_info"]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-02-07 16:14:15.833284","version":"1.26.0-18.0.pre.90"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"android":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":["device_info"]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-02-08 01:11:20.039088","version":"1.26.0-18.0.pre.90"} \ No newline at end of file diff --git a/example/ios/Flutter/Flutter.podspec b/example/ios/Flutter/Flutter.podspec deleted file mode 100644 index 2c4421cf..00000000 --- a/example/ios/Flutter/Flutter.podspec +++ /dev/null @@ -1,18 +0,0 @@ -# -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# This is a generated file; do not edit or check into version control. -# - -Pod::Spec.new do |s| - s.name = 'Flutter' - s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - # Framework linking is handled by Flutter tooling, not CocoaPods. - # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. - s.vendored_frameworks = 'path/to/nothing' -end diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh index 1d1cd30c..d22068ce 100755 --- a/example/ios/Flutter/flutter_export_environment.sh +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -2,13 +2,12 @@ # 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=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart" +export "FLUTTER_TARGET=lib/main.dart" export "FLUTTER_BUILD_DIR=build" export "SYMROOT=${SOURCE_ROOT}/../build/ios" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" -export "DART_DEFINES=flutter.inspector.structuredErrors%3Dtrue,FLUTTER_WEB_AUTO_DETECT%3Dtrue" export "DART_OBFUSCATION=false" -export "TRACK_WIDGET_CREATION=true" +export "TRACK_WIDGET_CREATION=false" export "TREE_SHAKE_ICONS=false" -export "PACKAGE_CONFIG=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/.dart_tool/package_config.json" +export "PACKAGE_CONFIG=.packages" diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index cd331cd8..4f231d14 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -14,6 +14,8 @@ class InAppWebViewExampleScreen extends StatefulWidget { } class _InAppWebViewExampleScreenState extends State { + final GlobalKey webViewKey = GlobalKey(); + InAppWebViewController? webView; late ContextMenu contextMenu; String url = ""; @@ -80,6 +82,7 @@ class _InAppWebViewExampleScreenState extends State { decoration: BoxDecoration(border: Border.all(color: Colors.blueAccent)), child: InAppWebView( + key: webViewKey, // contextMenu: contextMenu, initialUrl: "https://flutter.dev", // initialFile: "assets/index.html", @@ -135,6 +138,8 @@ class _InAppWebViewExampleScreenState extends State { setState(() { this.url = url ?? ''; }); + // RenderObject renderBox = webViewKey.currentContext!.findRenderObject()!; + // print(renderBox.paintBounds.size); }, onProgressChanged: (controller, progress) { setState(() { diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index 39a3e698..a8c7c861 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -1700,12 +1700,40 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi : currentIndex + steps >= 0 } - public func takeScreenshot (completionHandler: @escaping (_ screenshot: Data?) -> Void) { + public func takeScreenshot (with: [String: Any?]?, completionHandler: @escaping (_ screenshot: Data?) -> Void) { if #available(iOS 11.0, *) { - takeSnapshot(with: nil, completionHandler: {(image, error) -> Void in + var snapshotConfiguration: WKSnapshotConfiguration? = nil + if let with = with { + snapshotConfiguration = WKSnapshotConfiguration() + if let rect = with["rect"] as? [String: Double] { + snapshotConfiguration!.rect = CGRect(x: rect["x"]!, y: rect["y"]!, width: rect["width"]!, height: rect["height"]!) + } + if let snapshotWidth = with["snapshotWidth"] as? Double { + snapshotConfiguration!.snapshotWidth = NSNumber(value: snapshotWidth) + } + if #available(iOS 13.0, *), let afterScreenUpdates = with["iosAfterScreenUpdates"] as? Bool { + snapshotConfiguration!.afterScreenUpdates = afterScreenUpdates + } + } + takeSnapshot(with: snapshotConfiguration, completionHandler: {(image, error) -> Void in var imageData: Data? = nil if let screenshot = image { - imageData = screenshot.pngData()! + if let with = with { + switch with["compressFormat"] as! String { + case "JPEG": + let quality = Float(with["quality"] as! Int) / 100 + imageData = screenshot.jpegData(compressionQuality: CGFloat(quality))! + break + case "PNG": + imageData = screenshot.pngData()! + break + default: + imageData = screenshot.pngData()! + } + } + else { + imageData = screenshot.pngData()! + } } completionHandler(imageData) }) diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift index bc5ff20e..2a02ee42 100644 --- a/ios/Classes/InAppWebViewMethodHandler.swift +++ b/ios/Classes/InAppWebViewMethodHandler.swift @@ -129,7 +129,8 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { break case "takeScreenshot": if webView != nil { - webView!.takeScreenshot(completionHandler: { (screenshot) -> Void in + let screenshotConfiguration = arguments!["screenshotConfiguration"] as? [String: Any?] + webView!.takeScreenshot(with: screenshotConfiguration, completionHandler: { (screenshot) -> Void in result(screenshot) }) } diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart index 571da543..54852eb4 100644 --- a/lib/src/in_app_webview_controller.dart +++ b/lib/src/in_app_webview_controller.dart @@ -1504,13 +1504,16 @@ class InAppWebViewController { return this.javaScriptHandlersMap.remove(handlerName); } - ///Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a `Uint8List`. Returns `null` if it wasn't be able to take it. + ///Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a [Uint8List]. Returns `null` if it wasn't be able to take it. + /// + ///[screenshotConfiguration] represents the configuration data to use when generating an image from a web view’s contents. /// ///**NOTE for iOS**: available from iOS 11.0+. /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/2873260-takesnapshot - Future takeScreenshot() async { + Future takeScreenshot({ScreenshotConfiguration? screenshotConfiguration}) async { Map args = {}; + args.putIfAbsent('screenshotConfiguration', () => screenshotConfiguration?.toMap()); return await _channel.invokeMethod('takeScreenshot', args); } @@ -1716,7 +1719,8 @@ class InAppWebViewController { /// ///[zoomFactor] represents the zoom factor to apply. On Android, the zoom factor will be clamped to the Webview's zoom limits and, also, this value must be in the range 0.01 (excluded) to 100.0 (included). /// - ///[iosAnimated] `true` to animate the transition to the new scale, `false` to make the transition immediate. Available only on iOS. + ///[iosAnimated] `true` to animate the transition to the new scale, `false` to make the transition immediate. + ///**NOTE**: available only on iOS. /// ///**NOTE**: available on Android 21+. /// @@ -1748,7 +1752,8 @@ class InAppWebViewController { ///Gets the selected text. /// ///**NOTE**: This method is implemented with using JavaScript. - ///Available only on Android 19+. + /// + ///**NOTE for Android**: available only on Android 19+. Future getSelectedText() async { Map args = {}; return await _channel.invokeMethod('getSelectedText', args); diff --git a/lib/src/types.dart b/lib/src/types.dart index 841d2b5b..76e407e2 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -168,7 +168,7 @@ class InAppWebViewInitialData { } ///Class representing a resource request of the WebView used by the event [WebView.androidShouldInterceptRequest]. -///Available only on Android. +///**NOTE**: available only on Android. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebResourceRequest class WebResourceRequest { @@ -177,12 +177,12 @@ class WebResourceRequest { ///The headers associated with the request. /// - ///**NOTE**: Available on Android 21+. For Android < 21 it will be always `null`. + ///**NOTE**: available on Android 21+. For Android < 21 it will be always `null`. Map? headers; ///The method associated with the request, for example `GET`. /// - ///**NOTE**: Available on Android 21+. For Android < 21 it will be always "GET". + ///**NOTE**: available on Android 21+. For Android < 21 it will be always "GET". String? method; ///Gets whether a gesture (such as a click) was associated with the request. @@ -190,17 +190,17 @@ class WebResourceRequest { ///the sequence of events which caused the request to be created was initiated by a user ///gesture. /// - ///**NOTE**: Available on Android 21+. For Android < 21 it will be always `false`. + ///**NOTE**: available on Android 21+. For Android < 21 it will be always `false`. bool? hasGesture; ///Whether the request was made in order to fetch the main frame's document. /// - ///**NOTE**: Available on Android 21+. For Android < 21 it will be always `true`. + ///**NOTE**: available on Android 21+. For Android < 21 it will be always `true`. bool? isForMainFrame; ///Whether the request was a result of a server-side redirect. /// - ///**NOTE**: Available on Android 21+. For Android < 21 it will be always `false`. + ///**NOTE**: available on Android 21+. For Android < 21 it will be always `false`. bool? isRedirect; WebResourceRequest( @@ -233,7 +233,7 @@ class WebResourceRequest { } ///Class representing a resource response of the WebView used by the event [WebView.androidShouldInterceptRequest]. -///Available only on Android. +///**NOTE**: available only on Android. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebResourceResponse class WebResourceResponse { @@ -248,19 +248,19 @@ class WebResourceResponse { ///The headers for the resource response. If [headers] isn't `null`, then you need to set also [statusCode] and [reasonPhrase]. /// - ///**NOTE**: Available on Android 21+. For Android < 21 it won't be used. + ///**NOTE**: available on Android 21+. For Android < 21 it won't be used. Map? headers; ///The status code needs to be in the ranges [100, 299], [400, 599]. Causing a redirect by specifying a 3xx code is not supported. ///If statusCode is set, then you need to set also [headers] and [reasonPhrase]. This value cannot be `null`. /// - ///**NOTE**: Available on Android 21+. For Android < 21 it won't be used. + ///**NOTE**: available on Android 21+. For Android < 21 it won't be used. int? statusCode; ///The phrase describing the status code, for example `"OK"`. Must be non-empty. ///If reasonPhrase is set, then you need to set also [headers] and [reasonPhrase]. This value cannot be `null`. /// - ///**NOTE**: Available on Android 21+. For Android < 21 it won't be used. + ///**NOTE**: available on Android 21+. For Android < 21 it won't be used. String? reasonPhrase; WebResourceResponse( @@ -452,7 +452,9 @@ class JsAlertRequest { ///Message to be displayed in the window. String? message; - ///Indicates whether the request was made for the main frame. Available only on iOS. + ///Indicates whether the request was made for the main frame. + /// + ///**NOTE**: available only on iOS. bool? iosIsMainFrame; JsAlertRequest({this.url, this.message, this.iosIsMainFrame}); @@ -534,7 +536,9 @@ class JsConfirmRequest { ///Message to be displayed in the window. String? message; - ///Indicates whether the request was made for the main frame. Available only on iOS. + ///Indicates whether the request was made for the main frame. + /// + ///**NOTE**: available only on iOS. bool? iosIsMainFrame; JsConfirmRequest({this.url, this.message, this.iosIsMainFrame}); @@ -625,7 +629,9 @@ class JsPromptRequest { ///The default value displayed in the prompt dialog. String? defaultValue; - ///Indicates whether the request was made for the main frame. Available only on iOS. + ///Indicates whether the request was made for the main frame. + /// + ///**NOTE**: available only on iOS. bool? iosIsMainFrame; JsPromptRequest( @@ -729,7 +735,9 @@ class JsBeforeUnloadRequest { ///Message to be displayed in the window. String? message; - ///Indicates whether the request was made for the main frame. Available only on iOS. + ///Indicates whether the request was made for the main frame. + /// + ///**NOTE**: available only on iOS. bool? iosIsMainFrame; JsBeforeUnloadRequest({this.url, this.message, this.iosIsMainFrame}); @@ -3168,13 +3176,17 @@ class ShouldOverrideUrlLoadingRequest { ///the sequence of events which caused the request to be created was initiated by a user ///gesture. /// - ///Available only on Android. On Android < 24, this is always `false`. + ///**NOTE**: available only on Android. On Android < 24, this is always `false`. bool? androidHasGesture; - ///Gets whether the request was a result of a server-side redirect. Available only on Android. On Android < 21, this is always `false`. + ///Gets whether the request was a result of a server-side redirect. + /// + ///**NOTE**: available only on Android. On Android < 21, this is always `false`. bool? androidIsRedirect; - ///The type of action triggering the navigation. Available only on iOS. + ///The type of action triggering the navigation. + /// + ///**NOTE**: available only on iOS. IOSWKNavigationType? iosWKNavigationType; ShouldOverrideUrlLoadingRequest( @@ -3218,16 +3230,24 @@ class CreateWindowRequest { ///The window id. Used by [WebView] to create a new WebView. int windowId; - ///Indicates if the new window should be a dialog, rather than a full-size window. Available only on Android. + ///Indicates if the new window should be a dialog, rather than a full-size window. + /// + ///**NOTE**: available only on Android. bool? androidIsDialog; - ///Indicates if the request was initiated by a user gesture, such as the user clicking a link. Available only on Android. + ///Indicates if the request was initiated by a user gesture, such as the user clicking a link. + /// + ///**NOTE**: available only on Android. bool? androidIsUserGesture; - ///The type of action triggering the navigation. Available only on iOS. + ///The type of action triggering the navigation. + /// + ///**NOTE**: available only on iOS. IOSWKNavigationType? iosWKNavigationType; - ///Whether the request was made in order to fetch the main frame's document. Available only on iOS. + ///Whether the request was made in order to fetch the main frame's document. + /// + ///**NOTE**: available only on iOS. bool? iosIsForMainFrame; CreateWindowRequest( @@ -4568,7 +4588,9 @@ class UserScript { ///A Boolean value that indicates whether to inject the script into the main frame. ///Specify true to inject the script only into the main frame, or false to inject it into all frames. - ///The default value is `true`. Available only on iOS. + ///The default value is `true`. + /// + ///**NOTE**: available only on iOS. bool iosForMainFrameOnly; ///**NOTE for iOS 14.0+**: The namespace in which to evaluate the script. @@ -4656,6 +4678,169 @@ class CallAsyncJavaScriptResult { return this.toMap(); } + @override + String toString() { + return toMap().toString(); + } +} + +///A class that represents a structure that contains the location and dimensions of a rectangle. +class InAppWebViewRect { + /// + double x; + /// + double y; + /// + double width; + /// + double height; + + InAppWebViewRect({required this.x, required this.y, required this.width, required this.height}) { + assert(this.x >= 0 && this.y >= 0 && this.width >= 0 && this.height >= 0); + } + + Map toMap() { + return {"x": x, "y": y, "width": width, "height": height}; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} + +///Class that represents the known formats a bitmap can be compressed into. +class CompressFormat { + final String _value; + + const CompressFormat._internal(this._value); + + static final Set values = [ + CompressFormat.JPEG, + CompressFormat.PNG, + CompressFormat.WEBP, + CompressFormat.WEBP_LOSSY, + CompressFormat.WEBP_LOSSLESS, + ].toSet(); + + static CompressFormat? fromValue(String? value) { + if (value != null) { + try { + return CompressFormat.values.firstWhere( + (element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + String toValue() => _value; + + @override + String toString() => _value; + + ///Compress to the `PNG` format. + ///PNG is lossless, so `quality` is ignored. + static const PNG = const CompressFormat._internal("PNG"); + + ///Compress to the `JPEG` format. + ///Quality of `0` means compress for the smallest size. + ///`100` means compress for max visual quality. + static const JPEG = const CompressFormat._internal("JPEG"); + + ///Compress to the `WEBP` lossy format. + ///Quality of `0` means compress for the smallest size. + ///`100` means compress for max visual quality. + /// + ///**NOTE**: available only on Android. + static const WEBP = const CompressFormat._internal("WEBP"); + + ///Compress to the `WEBP` lossy format. + ///Quality of `0` means compress for the smallest size. + ///`100` means compress for max visual quality. + /// + ///**NOTE**: available only on Android. + /// + ///**NOTE for Android**: available on Android 30+. + static const WEBP_LOSSY = const CompressFormat._internal("WEBP_LOSSY"); + + ///Compress to the `WEBP` lossless format. + ///Quality refers to how much effort to put into compression. + ///A value of `0` means to compress quickly, resulting in a relatively large file size. + ///`100` means to spend more time compressing, resulting in a smaller file. + /// + ///**NOTE**: available only on Android. + /// + ///**NOTE for Android**: available on Android 30+. + static const WEBP_LOSSLESS = const CompressFormat._internal("WEBP_LOSSLESS"); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} + + +///Class that represents the configuration data to use when generating an image from a web view’s contents using [InAppWebViewController.takeScreenshot]. +/// +///**NOTE for iOS**: available from iOS 11.0+. +class ScreenshotConfiguration { + ///The portion of your web view to capture, specified as a rectangle in the view’s coordinate system. + ///The default value of this property is `null`, which captures everything in the view’s bounds rectangle. + ///If you specify a custom rectangle, it must lie within the bounds rectangle of the [WebView] object. + InAppWebViewRect? rect; + + ///The width of the captured image, in points. + ///Use this property to scale the generated image to the specified width. + ///The web view maintains the aspect ratio of the captured content, but scales it to match the width you specify. + /// + ///The default value of this property is `null`, which returns an image whose size matches the original size of the captured rectangle. + double? snapshotWidth; + + ///The compression format of the captured image. + ///The default value is [CompressFormat.PNG]. + CompressFormat compressFormat; + + ///Hint to the compressor, `0-100`. The value is interpreted differently depending on the [CompressFormat]. + ///[CompressFormat.PNG] is lossless, so this value is ignored. + int quality; + + ///A Boolean value that indicates whether to take the snapshot after incorporating any pending screen updates. + ///The default value of this property is `true`, which causes the web view to incorporate any recent changes to the view’s content and then generate the snapshot. + ///If you change the value to `false`, the [WebView] takes the snapshot immediately, and before incorporating any new changes. + /// + ///**NOTE**: available only on iOS. + /// + ///**NOTE for iOS**: available only on iOS. Available from iOS 13.0+. + bool iosAfterScreenUpdates; + + ScreenshotConfiguration({ + this.rect, + this.snapshotWidth, + this.compressFormat = CompressFormat.PNG, + this.quality = 100, + this.iosAfterScreenUpdates = true}) { + assert(this.quality >= 0); + } + + Map toMap() { + return { + "rect": rect?.toMap(), + "snapshotWidth": snapshotWidth, + "compressFormat": compressFormat.toValue(), + "quality": quality, + "iosAfterScreenUpdates": iosAfterScreenUpdates}; + } + + Map toJson() { + return this.toMap(); + } + @override String toString() { return toMap().toString();