From 88bfe9036f5e8fe1c84ce7783c766a81828b73ee Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Tue, 9 Feb 2021 01:39:35 +0100 Subject: [PATCH] added createWebArchiveData iOS-specific WebView method, Moved saveWebArchive WebView method from Android-specific to cross-platform --- CHANGELOG.md | 3 +- README.md | 1 + .../InAppWebViewMethodHandler.java | 4 +- example/.flutter-plugins-dependencies | 2 +- example/ios/Runner.xcodeproj/project.pbxproj | 2 - example/ios/Runner/Info.plist | 6 +- example/lib/in_app_webiew_example.screen.dart | 1 + example/lib/main.dart | 1 + ios/Classes/InAppWebView.swift | 149 ++++++++++++------ ios/Classes/InAppWebViewMethodHandler.swift | 26 ++- lib/src/in_app_webview_controller.dart | 64 +++++--- lib/src/types.dart | 40 +++++ 12 files changed, 220 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 654c23ab..d6381ff0 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - 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`, `createPdf` iOS-specific WebView methods +- Added `handlesURLScheme`, `createPdf`, `createWebArchiveData` iOS-specific WebView methods - Added `iosAnimated` optional argument to `zoomBy` WebView method - Added `screenshotConfiguration` optional argument to `takeScreenshot` WebView method - Updated integration tests @@ -44,6 +44,7 @@ - `allowUniversalAccessFromFileURLs` and `allowFileAccessFromFileURLs` WebView options moved from Android-specific options to cross-platform options - Added `callAsyncJavaScript` name to the list of javaScriptHandlerForbiddenNames - Changed `zoomBy` WebView method signature +- Moved `saveWebArchive` WebView method from Android-specific to cross-platform ## 4.0.0+4 diff --git a/README.md b/README.md index 19f66a21..52a4027b 100755 --- a/README.md +++ b/README.md @@ -494,6 +494,7 @@ Android-specific methods can be called using the `InAppWebViewController.android iOS-specific methods can be called using the `InAppWebViewController.ios` attribute. Static methods can be called using the `IOSInAppWebViewController` class directly. * `createPdf({IOSWKPDFConfiguration? iosWKPdfConfiguration})`: Generates PDF data from the web view’s contents asynchronously. +* `createWebArchiveData`: Creates a web archive of the web view’s current contents asynchronously. * `hasOnlySecureContent`: A Boolean value indicating whether all resources on the page have been loaded over securely encrypted connections. * `reloadFromOrigin`: Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible. * `static handlesURLScheme(String urlScheme)`: Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme. 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 9fa5d55a..79fb2e16 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java @@ -338,9 +338,9 @@ public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandle break; case "saveWebArchive": if (webView != null) { - String basename = (String) call.argument("basename"); + String filePath = (String) call.argument("filePath"); boolean autoname = (boolean) call.argument("autoname"); - webView.saveWebArchive(basename, autoname, new ValueCallback() { + webView.saveWebArchive(filePath, autoname, new ValueCallback() { @Override public void onReceiveValue(String value) { result.success(value); diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index d20f8952..8e77e80c 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-08 18:26:05.315620","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-09 01:23:18.308862","version":"1.26.0-18.0.pre.90"} \ No newline at end of file diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index c967f816..d3fe73ee 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -262,7 +262,6 @@ "${BUILT_PRODUCTS_DIR}/flutter_inappwebview/flutter_inappwebview.framework", "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", - "${BUILT_PRODUCTS_DIR}/pdf_viewer_plugin/pdf_viewer_plugin.framework", "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", ); name = "[CP] Embed Pods Frameworks"; @@ -272,7 +271,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_inappwebview.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pdf_viewer_plugin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", ); runOnlyForDeploymentPostprocessing = 0; diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 5daa91b8..df18581f 100755 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,8 +2,10 @@ + NSDocumentsFolderUsageDescription + InAppWebView requires access to documents folder NSMicrophoneUsageDescription - InAppWebView requires acess to mic. + InAppWebView requires access to mic. CFBundleDevelopmentRegion en CFBundleExecutable @@ -64,7 +66,7 @@ UIViewControllerBasedStatusBarAppearance NSCameraUsageDescription - InAppWebView requires acess to cam. + InAppWebView requires access to cam. NSLocalNetworkUsageDescription Allow Flutter tools on your computer to connect and debug your application. diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index 6375e057..079cd6b2 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +// import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'main.dart'; diff --git a/example/lib/main.dart b/example/lib/main.dart index 19b5149a..ed68cb64 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -18,6 +18,7 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); // await Permission.camera.request(); // await Permission.microphone.request(); + // await Permission.storage.request(); if (Platform.isAndroid) { await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true); diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index 44289415..1f182c79 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -1700,70 +1700,117 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi : currentIndex + steps >= 0 } + @available(iOS 11.0, *) public func takeScreenshot (with: [String: Any?]?, completionHandler: @escaping (_ screenshot: Data?) -> Void) { - if #available(iOS 11.0, *) { - 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 - } + 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"]!) } - takeSnapshot(with: snapshotConfiguration, completionHandler: {(image, error) -> Void in - var imageData: Data? = nil - if let screenshot = image { - 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 { + 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 { + 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()! } } - completionHandler(imageData) - }) - } else { - completionHandler(nil) + else { + imageData = screenshot.pngData()! + } + } + completionHandler(imageData) + }) + } + + @available(iOS 14.0, *) + public func createPdf (configuration: [String: Any?]?, completionHandler: @escaping (_ pdf: Data?) -> Void) { + let pdfConfiguration: WKPDFConfiguration = .init() + if let configuration = configuration { + if let rect = configuration["rect"] as? [String: Double] { + pdfConfiguration.rect = CGRect(x: rect["x"]!, y: rect["y"]!, width: rect["width"]!, height: rect["height"]!) + } + } + createPDF(configuration: pdfConfiguration) { (result) in + switch (result) { + case .success(let data): + completionHandler(data) + return + case .failure(let error): + print(error.localizedDescription) + completionHandler(nil) + return + } } } - public func createPdf (configuration: [String: Any?]?, completionHandler: @escaping (_ pdf: Data?) -> Void) { - if #available(iOS 14.0, *) { - let pdfConfiguration: WKPDFConfiguration = .init() - if let configuration = configuration { - if let rect = configuration["rect"] as? [String: Double] { - pdfConfiguration.rect = CGRect(x: rect["x"]!, y: rect["y"]!, width: rect["width"]!, height: rect["height"]!) - } + @available(iOS 14.0, *) + public func createWebArchiveData (dataCompletionHandler: @escaping (_ webArchiveData: Data?) -> Void) { + createWebArchiveData(completionHandler: { (result) in + switch (result) { + case .success(let data): + dataCompletionHandler(data) + return + case .failure(let error): + print(error.localizedDescription) + dataCompletionHandler(nil) + return } - createPDF(configuration: pdfConfiguration) { (result) in - switch (result) { - case .success(let data): - completionHandler(data) - return - case .failure(let error): + }) + } + + @available(iOS 14.0, *) + public func saveWebArchive (filePath: String, autoname: Bool, completionHandler: @escaping (_ path: String?) -> Void) { + createWebArchiveData(dataCompletionHandler: { (webArchiveData) in + if let webArchiveData = webArchiveData { + var localUrl = URL(fileURLWithPath: filePath) + if autoname { + if let url = self.url { + // tries to mimic Android saveWebArchive method + let invalidCharacters = CharacterSet(charactersIn: "\\/:*?\"<>|") + .union(.newlines) + .union(.illegalCharacters) + .union(.controlCharacters) + + let currentPageUrlFileName = url.path + .components(separatedBy: invalidCharacters) + .joined(separator: "") + + let fullPath = filePath + "/" + currentPageUrlFileName + ".webarchive" + localUrl = URL(fileURLWithPath: fullPath) + } else { + completionHandler(nil) + return + } + } + do { + try webArchiveData.write(to: localUrl) + completionHandler(localUrl.path) + } catch { + // Catch any errors print(error.localizedDescription) completionHandler(nil) - return } + } else { + completionHandler(nil) } - } else { - completionHandler(nil) - } + }) } public func loadUrl(url: URL, headers: [String: String]?) { diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift index eebb3c52..2b154154 100644 --- a/ios/Classes/InAppWebViewMethodHandler.swift +++ b/ios/Classes/InAppWebViewMethodHandler.swift @@ -128,7 +128,7 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { result(webView?.isLoading ?? false) break case "takeScreenshot": - if webView != nil { + if webView != nil, #available(iOS 11.0, *) { let screenshotConfiguration = arguments!["screenshotConfiguration"] as? [String: Any?] webView!.takeScreenshot(with: screenshotConfiguration, completionHandler: { (screenshot) -> Void in result(screenshot) @@ -403,7 +403,7 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { } break case "createPdf": - if webView != nil { + if webView != nil, #available(iOS 14.0, *) { let configuration = arguments!["iosWKPdfConfiguration"] as? [String: Any?] webView!.createPdf(configuration: configuration, completionHandler: { (pdf) -> Void in result(pdf) @@ -413,6 +413,28 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { result(nil) } break + case "createWebArchiveData": + if webView != nil, #available(iOS 14.0, *) { + webView!.createWebArchiveData(dataCompletionHandler: { (webArchiveData) -> Void in + result(webArchiveData) + }) + } + else { + result(nil) + } + break + case "saveWebArchive": + if webView != nil, #available(iOS 14.0, *) { + let filePath = arguments!["filePath"] as! String + let autoname = arguments!["autoname"] as! Bool + webView!.saveWebArchive(filePath: filePath, autoname: autoname, completionHandler: { (path) -> Void in + result(path) + }) + } + else { + result(nil) + } + break default: result(FlutterMethodNotImplemented) break diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart index edf163c9..079a4234 100644 --- a/lib/src/in_app_webview_controller.dart +++ b/lib/src/in_app_webview_controller.dart @@ -1508,7 +1508,7 @@ class InAppWebViewController { /// ///[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+. + ///**NOTE for iOS**: available on iOS 11.0+. /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/2873260-takesnapshot Future takeScreenshot({ScreenshotConfiguration? screenshotConfiguration}) async { @@ -2088,6 +2088,35 @@ class InAppWebViewController { return CallAsyncJavaScriptResult(value: data["value"], error: data["error"]); } + ///Saves the current WebView as a web archive. + ///Returns the file path under which the web archive file was saved, or `null` if saving the file failed. + /// + ///[filePath] represents the file path where the archive should be placed. This value cannot be `null`. + /// + ///[autoname] if `false`, takes [filePath] to be a file. + ///If `true`, [filePath] is assumed to be a directory in which a filename will be chosen according to the URL of the current page. + /// + ///**NOTE for iOS**: Available on iOS 14.0+. If [autoname] is `false`, the [filePath] must ends with the [WebArchiveFormat.WEBARCHIVE] file extension. + /// + ///**NOTE for Android**: if [autoname] is `false`, the [filePath] must ends with the [WebArchiveFormat.MHT] file extension. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#saveWebArchive(java.lang.String,%20boolean,%20android.webkit.ValueCallback%3Cjava.lang.String%3E) + Future saveWebArchive( + {required String filePath, bool autoname = false}) async { + if (!autoname) { + if (Platform.isAndroid) { + assert(filePath.endsWith("." + WebArchiveFormat.MHT.toValue())); + } else if (Platform.isIOS) { + assert(filePath.endsWith("." + WebArchiveFormat.WEBARCHIVE.toValue())); + } + } + + Map args = {}; + args.putIfAbsent("filePath", () => filePath); + args.putIfAbsent("autoname", () => autoname); + return await _channel.invokeMethod('saveWebArchive', args); + } + ///Gets the default user agent. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context) @@ -2180,23 +2209,6 @@ class AndroidInAppWebViewController { return await _controller._channel.invokeMethod('pageUp', args); } - ///Saves the current WebView as a web archive. - ///Returns the filename under which the file was saved, or `null` if saving the file failed. - /// - ///[basename] the filename where the archive should be placed. This value cannot be `null`. - /// - ///[autoname] if `false`, takes basename to be a file. - ///If `true`, [basename] is assumed to be a directory in which a filename will be chosen according to the URL of the current page. - /// - ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#saveWebArchive(java.lang.String,%20boolean,%20android.webkit.ValueCallback%3Cjava.lang.String%3E) - Future saveWebArchive( - {required String basename, required bool autoname}) async { - Map args = {}; - args.putIfAbsent("basename", () => basename); - args.putIfAbsent("autoname", () => autoname); - return await _controller._channel.invokeMethod('saveWebArchive', args); - } - ///Performs zoom in in this WebView. ///Returns `true` if zoom in succeeds, `false` if no zoom changes. /// @@ -2337,14 +2349,30 @@ class IOSInAppWebViewController { } ///Generates PDF data from the web view’s contents asynchronously. + ///Returns `null` if a problem occurred. /// ///[iosWKPdfConfiguration] represents the object that specifies the portion of the web view to capture as PDF data. + /// + ///**NOTE**: available only on iOS 14.0+. + /// + ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/3650490-createpdf Future createPdf({IOSWKPDFConfiguration? iosWKPdfConfiguration}) async { Map args = {}; args.putIfAbsent('iosWKPdfConfiguration', () => iosWKPdfConfiguration?.toMap()); return await _controller._channel.invokeMethod('createPdf', args); } + ///Creates a web archive of the web view’s current contents asynchronously. + ///Returns `null` if a problem occurred. + /// + ///**NOTE**: available only on iOS 14.0+. + /// + ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/3650491-createwebarchivedata + Future createWebArchiveData() async { + Map args = {}; + return await _controller._channel.invokeMethod('createWebArchiveData', args); + } + ///Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme. /// ///[urlScheme] represents the URL scheme associated with the resource. diff --git a/lib/src/types.dart b/lib/src/types.dart index 60b76289..c11f4840 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -4873,4 +4873,44 @@ class IOSWKPDFConfiguration { String toString() { return toMap().toString(); } +} + +///Class that represents the known Web Archive formats used when saving a web page. +class WebArchiveFormat { + final String _value; + + const WebArchiveFormat._internal(this._value); + + static final Set values = [ + WebArchiveFormat.MHT, + WebArchiveFormat.WEBARCHIVE + ].toSet(); + + static WebArchiveFormat? fromValue(String? value) { + if (value != null) { + try { + return WebArchiveFormat.values.firstWhere( + (element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + String toValue() => _value; + + @override + String toString() => _value; + + ///Web Archive format used only by Android. + static const MHT = const WebArchiveFormat._internal("mht"); + + ///Web Archive format used only by iOS. + static const WEBARCHIVE = const WebArchiveFormat._internal("webarchive"); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; } \ No newline at end of file