From 0dd9dfbff631bbb5bb9886ba46ffd8451a5407e6 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Thu, 12 May 2022 00:19:43 +0200 Subject: [PATCH] added PrintJobController to manage print jobs --- CHANGELOG.md | 1 + .../flutter_inappwebview/ISettings.java | 8 +- .../InAppWebViewFlutterPlugin.java | 28 +- .../ChromeCustomTabsSettings.java | 8 +- .../in_app_browser/InAppBrowserSettings.java | 14 +- .../plugin_scripts_js/PrintJS.java | 2 +- .../print_job/PrintJobChannelDelegate.java | 70 +++ .../print_job/PrintJobController.java | 86 ++++ .../print_job/PrintJobManager.java | 53 ++ .../print_job/PrintJobSettings.java | 99 ++++ .../proxy/ProxySettings.java | 8 +- .../PullToRefreshSettings.java | 9 +- .../types/MarginsExt.java | 150 ++++++ .../types/MediaSizeExt.java | 134 ++++++ .../types/PrintAttributesExt.java | 56 +++ .../types/PrintJobInfoExt.java | 55 +++ .../types/ResolutionExt.java | 134 ++++++ .../webview/ContextMenuSettings.java | 10 +- .../webview/InAppWebViewInterface.java | 4 +- .../webview/JavaScriptBridgeInterface.java | 34 +- .../webview/WebViewChannelDelegate.java | 31 +- .../webview/in_app_webview/InAppWebView.java | 65 ++- .../in_app_webview/InAppWebViewSettings.java | 8 +- .../web_message/WebMessageChannel.java | 3 +- .../WebMessageChannelChannelDelegate.java | 6 +- .../WebMessageListenerChannelDelegate.java | 2 +- .../in_app_webview/on_print.dart | 3 +- example/lib/in_app_webiew_example.screen.dart | 2 +- ios/Classes/ISettings.swift | 26 +- ios/Classes/InAppWebView/InAppWebView.swift | 104 +++- .../InAppWebView/InAppWebViewSettings.swift | 4 +- .../InAppWebView/WebViewChannelDelegate.swift | 36 +- ios/Classes/PluginScriptsJS/PrintJS.swift | 2 +- .../PrintJob/CustomUIPrintPageRenderer.swift | 34 ++ ios/Classes/PrintJob/PrintAttributes.swift | 56 +++ .../PrintJob/PrintJobChannelDelegate.swift | 68 +++ ios/Classes/PrintJob/PrintJobController.swift | 96 ++++ ios/Classes/PrintJob/PrintJobInfo.swift | 43 ++ ios/Classes/PrintJob/PrintJobManager.swift | 28 ++ ios/Classes/PrintJob/PrintJobSettings.swift | 154 ++++++ ios/Classes/SwiftFlutterPlugin.swift | 4 + ios/Classes/Types/CGRect.swift | 23 + ios/Classes/Types/UIEdgeInsets.swift | 23 + lib/assets/web/web_support.js | 2 +- lib/src/in_app_browser/in_app_browser.dart | 13 +- .../headless_in_app_webview.dart | 16 +- lib/src/in_app_webview/in_app_webview.dart | 16 +- .../in_app_webview_controller.dart | 67 ++- lib/src/in_app_webview/main.dart | 2 +- lib/src/in_app_webview/webview.dart | 31 +- lib/src/main.dart | 1 + lib/src/print_job/main.dart | 2 + lib/src/print_job/print_job_controller.dart | 103 ++++ lib/src/print_job/print_job_settings.dart | 250 ++++++++++ lib/src/types/in_app_webview_rect.dart | 14 + lib/src/types/main.dart | 12 +- lib/src/types/permission_resource_type.dart | 2 +- lib/src/types/print_job_attributes.dart | 173 +++++++ lib/src/types/print_job_color_mode.dart | 52 ++ lib/src/types/print_job_duplex_mode.dart | 90 ++++ lib/src/types/print_job_info.dart | 104 ++++ lib/src/types/print_job_media_size.dart | 455 ++++++++++++++++++ lib/src/types/print_job_orientation.dart | 52 ++ lib/src/types/print_job_output_type.dart | 74 +++ .../types/print_job_rendering_quality.dart | 53 ++ lib/src/types/print_job_resolution.dart | 64 +++ lib/src/types/print_job_state.dart | 123 +++++ lib/src/types/ssl_error_type.dart | 2 +- lib/src/types/web_resource_error_type.dart | 2 +- lib/src/util.dart | 18 + lib/src/web/in_app_web_view_web_element.dart | 10 +- lib/src/web/web_platform.dart | 4 +- 72 files changed, 3464 insertions(+), 127 deletions(-) create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobChannelDelegate.java create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobController.java create mode 100755 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobManager.java create mode 100755 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobSettings.java create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/MarginsExt.java create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/MediaSizeExt.java create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PrintAttributesExt.java create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PrintJobInfoExt.java create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/ResolutionExt.java create mode 100644 ios/Classes/PrintJob/CustomUIPrintPageRenderer.swift create mode 100644 ios/Classes/PrintJob/PrintAttributes.swift create mode 100644 ios/Classes/PrintJob/PrintJobChannelDelegate.swift create mode 100644 ios/Classes/PrintJob/PrintJobController.swift create mode 100644 ios/Classes/PrintJob/PrintJobInfo.swift create mode 100644 ios/Classes/PrintJob/PrintJobManager.swift create mode 100644 ios/Classes/PrintJob/PrintJobSettings.swift create mode 100644 ios/Classes/Types/CGRect.swift create mode 100644 ios/Classes/Types/UIEdgeInsets.swift create mode 100644 lib/src/print_job/main.dart create mode 100644 lib/src/print_job/print_job_controller.dart create mode 100644 lib/src/print_job/print_job_settings.dart create mode 100644 lib/src/types/print_job_attributes.dart create mode 100644 lib/src/types/print_job_color_mode.dart create mode 100644 lib/src/types/print_job_duplex_mode.dart create mode 100644 lib/src/types/print_job_info.dart create mode 100644 lib/src/types/print_job_media_size.dart create mode 100644 lib/src/types/print_job_orientation.dart create mode 100644 lib/src/types/print_job_output_type.dart create mode 100644 lib/src/types/print_job_rendering_quality.dart create mode 100644 lib/src/types/print_job_resolution.dart create mode 100644 lib/src/types/print_job_state.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 53bf87f1..451997e5 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Deprecated old classes/properties/methods to make them eventually compatible with other Platforms and WebView engines. - Added Web support - Added `ProxyController` for Android +- Added `PrintJobController` to manage print jobs - Added `WebAuthenticationSession` for iOS - Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState`, `isInFullscreen`, `getCameraCaptureState`, `setCameraCaptureState`, `getMicrophoneCaptureState`, `setMicrophoneCaptureState` WebView controller methods - Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS`, `forceDarkStrategy` WebView settings diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ISettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ISettings.java index e6d326ab..82972ae3 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ISettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ISettings.java @@ -1,9 +1,11 @@ package com.pichillilorenzo.flutter_inappwebview; +import androidx.annotation.NonNull; + import java.util.Map; public interface ISettings { - public ISettings parse(Map settings); - public Map toMap(); - public Map getRealSettings(T obj); + @NonNull ISettings parse(@NonNull Map settings); + @NonNull Map toMap(); + @NonNull Map getRealSettings(@NonNull T obj); } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java index 55ef9ff4..886b3da8 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java @@ -6,12 +6,14 @@ import android.net.Uri; import android.os.Build; import android.webkit.ValueCallback; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeSafariBrowserManager; import com.pichillilorenzo.flutter_inappwebview.credential_database.CredentialDatabaseHandler; import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserManager; import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebViewManager; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager; import com.pichillilorenzo.flutter_inappwebview.proxy.ProxyManager; import com.pichillilorenzo.flutter_inappwebview.service_worker.ServiceWorkerManager; @@ -27,21 +29,36 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { protected static final String LOG_TAG = "InAppWebViewFlutterPL"; + @Nullable public PlatformUtil platformUtil; + @Nullable public InAppBrowserManager inAppBrowserManager; + @Nullable public HeadlessInAppWebViewManager headlessInAppWebViewManager; + @Nullable public ChromeSafariBrowserManager chromeSafariBrowserManager; + @Nullable public InAppWebViewStatic inAppWebViewStatic; + @Nullable public MyCookieManager myCookieManager; + @Nullable public CredentialDatabaseHandler credentialDatabaseHandler; + @Nullable public MyWebStorage myWebStorage; + @Nullable public ServiceWorkerManager serviceWorkerManager; + @Nullable public WebViewFeatureManager webViewFeatureManager; + @Nullable public ProxyManager proxyManager; - public FlutterWebViewFactory flutterWebViewFactory; + @Nullable + public PrintJobManager printJobManager; + @Nullable public static ValueCallback filePathCallbackLegacy; + @Nullable public static ValueCallback filePathCallback; + public FlutterWebViewFactory flutterWebViewFactory; public Context applicationContext; public PluginRegistry.Registrar registrar; public BinaryMessenger messenger; @@ -101,10 +118,13 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { } webViewFeatureManager = new WebViewFeatureManager(this); proxyManager = new ProxyManager(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + printJobManager = new PrintJobManager(); + } } @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { if (platformUtil != null) { platformUtil.dispose(); platformUtil = null; @@ -149,6 +169,10 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { proxyManager.dispose(); proxyManager = null; } + if (printJobManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + printJobManager.dispose(); + printJobManager = null; + } filePathCallbackLegacy = null; filePathCallback = null; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsSettings.java index f8af7c91..0f9f4e7b 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsSettings.java @@ -2,6 +2,7 @@ package com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs; import android.content.Intent; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.trusted.ScreenOrientation; @@ -35,8 +36,9 @@ public class ChromeCustomTabsSettings implements ISettings options) { + public ChromeCustomTabsSettings parse(@NonNull Map options) { for (Map.Entry pair : options.entrySet()) { String key = pair.getKey(); Object value = pair.getValue(); @@ -104,6 +106,7 @@ public class ChromeCustomTabsSettings implements ISettings toMap() { Map options = new HashMap<>(); @@ -122,8 +125,9 @@ public class ChromeCustomTabsSettings implements ISettings getRealSettings(ChromeCustomTabsActivity chromeCustomTabsActivity) { + public Map getRealSettings(@NonNull ChromeCustomTabsActivity chromeCustomTabsActivity) { Map realOptions = toMap(); if (chromeCustomTabsActivity != null) { Intent intent = chromeCustomTabsActivity.getIntent(); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserSettings.java index 682f1820..ff7ce54d 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserSettings.java @@ -1,5 +1,6 @@ package com.pichillilorenzo.flutter_inappwebview.in_app_browser; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.pichillilorenzo.flutter_inappwebview.ISettings; @@ -26,8 +27,9 @@ public class InAppBrowserSettings implements ISettings { public Boolean allowGoBackWithBackButton = true; public Boolean shouldCloseOnBackButtonPressed = false; + @NonNull @Override - public InAppBrowserSettings parse(Map settings) { + public InAppBrowserSettings parse(@NonNull Map settings) { for (Map.Entry pair : settings.entrySet()) { String key = pair.getKey(); Object value = pair.getValue(); @@ -72,6 +74,7 @@ public class InAppBrowserSettings implements ISettings { return this; } + @NonNull @Override public Map toMap() { Map settings = new HashMap<>(); @@ -88,12 +91,13 @@ public class InAppBrowserSettings implements ISettings { return settings; } + @NonNull @Override - public Map getRealSettings(InAppBrowserActivity inAppBrowserActivity) { + public Map getRealSettings(@NonNull InAppBrowserActivity inAppBrowserActivity) { Map realSettings = toMap(); - realSettings.put("hideToolbarTop", !inAppBrowserActivity.actionBar.isShowing()); - realSettings.put("hideUrlBar", !inAppBrowserActivity.menu.findItem(R.id.menu_search).isVisible()); - realSettings.put("hideProgressBar", inAppBrowserActivity.progressBar.getMax() == 0); + realSettings.put("hideToolbarTop", inAppBrowserActivity.actionBar == null || !inAppBrowserActivity.actionBar.isShowing()); + realSettings.put("hideUrlBar", inAppBrowserActivity.menu == null || !inAppBrowserActivity.menu.findItem(R.id.menu_search).isVisible()); + realSettings.put("hideProgressBar", inAppBrowserActivity.progressBar == null || inAppBrowserActivity.progressBar.getMax() == 0); return realSettings; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PrintJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PrintJS.java index d8f7d18f..0af901d4 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PrintJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PrintJS.java @@ -15,7 +15,7 @@ public class PrintJS { public static final String PRINT_JS_SOURCE = "window.print = function() {" + " if (window.top == null || window.top === window) {" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onPrint', window.location.href);" + + " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onPrintRequest', window.location.href);" + " } else {" + " window.top.print();" + " }" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobChannelDelegate.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobChannelDelegate.java new file mode 100644 index 00000000..0984174b --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobChannelDelegate.java @@ -0,0 +1,70 @@ +package com.pichillilorenzo.flutter_inappwebview.print_job; + +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl; +import com.pichillilorenzo.flutter_inappwebview.types.PrintJobInfoExt; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class PrintJobChannelDelegate extends ChannelDelegateImpl { + @Nullable + private PrintJobController printJobController; + + public PrintJobChannelDelegate(@NonNull PrintJobController printJobController, @NonNull MethodChannel channel) { + super(channel); + this.printJobController = printJobController; + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + switch (call.method) { + case "cancel": + if (printJobController != null) { + printJobController.cancel(); + result.success(true); + } else { + result.success(false); + } + break; + case "restart": + if (printJobController != null) { + printJobController.restart(); + result.success(true); + } else { + result.success(false); + } + break; + case "getInfo": + if (printJobController != null) { + PrintJobInfoExt info = printJobController.getInfo(); + result.success(info != null ? info.toMap() : null); + } else { + result.success(null); + } + break; + case "dispose": + if (printJobController != null) { + printJobController.dispose(); + result.success(true); + } else { + result.success(false); + } + break; + default: + result.notImplemented(); + } + } + + @Override + public void dispose() { + super.dispose(); + printJobController = null; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobController.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobController.java new file mode 100644 index 00000000..28acd07a --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobController.java @@ -0,0 +1,86 @@ +package com.pichillilorenzo.flutter_inappwebview.print_job; + +import android.os.Build; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview.types.Disposable; +import com.pichillilorenzo.flutter_inappwebview.types.PrintJobInfoExt; + +import io.flutter.plugin.common.MethodChannel; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class PrintJobController implements Disposable { + protected static final String LOG_TAG = "PrintJob"; + public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_"; + + @NonNull + public String id; + @Nullable + public PrintJobChannelDelegate channelDelegate; + @Nullable + public android.print.PrintJob job; + @Nullable + public PrintJobSettings settings; + + public PrintJobController(@NonNull String id, @NonNull android.print.PrintJob job, + @Nullable PrintJobSettings settings, @NonNull InAppWebViewFlutterPlugin plugin) { + this.id = id; + this.job = job; + this.settings = settings; + final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id); + this.channelDelegate = new PrintJobChannelDelegate(this, channel); + } + + public void cancel() { + if (this.job != null) { + this.job.cancel(); + } + } + + public void restart() { + if (this.job != null) { + this.job.restart(); + } + } + + @Nullable + public PrintJobInfoExt getInfo() { + if (this.job != null) { + return PrintJobInfoExt.fromPrintJobInfo(this.job.getInfo()); + } + return null; + } + + public void disposeNoCancel() { + if (channelDelegate != null) { + channelDelegate.dispose(); + channelDelegate = null; + } + if (PrintJobManager.jobs.containsKey(id)) { + PrintJobManager.jobs.put(id, null); + } + if (job != null) { + job = null; + } + } + + @Override + public void dispose() { + if (channelDelegate != null) { + channelDelegate.dispose(); + channelDelegate = null; + } + if (PrintJobManager.jobs.containsKey(id)) { + PrintJobManager.jobs.put(id, null); + } + if (job != null) { + job.cancel(); + job = null; + } + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobManager.java new file mode 100755 index 00000000..fa4dcee4 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobManager.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package com.pichillilorenzo.flutter_inappwebview.print_job; + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import com.pichillilorenzo.flutter_inappwebview.types.Disposable; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class PrintJobManager implements Disposable { + protected static final String LOG_TAG = "PrintJobManager"; + + public static final Map jobs = new HashMap<>(); + + public PrintJobManager() { + super(); + } + + public void dispose() { + Collection printJobControllers = jobs.values(); + for (PrintJobController job : printJobControllers) { + if (job != null) { + job.dispose(); + } + } + jobs.clear(); + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobSettings.java new file mode 100755 index 00000000..25464c04 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobSettings.java @@ -0,0 +1,99 @@ +package com.pichillilorenzo.flutter_inappwebview.print_job; + +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import com.pichillilorenzo.flutter_inappwebview.ISettings; +import com.pichillilorenzo.flutter_inappwebview.types.MediaSizeExt; +import com.pichillilorenzo.flutter_inappwebview.types.ResolutionExt; + +import java.util.HashMap; +import java.util.Map; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class PrintJobSettings implements ISettings { + + public static final String LOG_TAG = "PrintJobSettings"; + + public Boolean handledByClient = false; + @Nullable + public String jobName; + @Nullable + public Integer orientation; +// @Nullable +// public MarginsExt margins; + @Nullable + public MediaSizeExt mediaSize; + @Nullable + public Integer colorMode; + @Nullable + public Integer duplexMode; + @Nullable + public ResolutionExt resolution; + + @NonNull + @Override + public PrintJobSettings parse(@NonNull Map settings) { + for (Map.Entry pair : settings.entrySet()) { + String key = pair.getKey(); + Object value = pair.getValue(); + if (value == null) { + continue; + } + + switch (key) { + case "handledByClient": + handledByClient = (Boolean) value; + break; + case "jobName": + jobName = (String) value; + break; + case "orientation": + orientation = (Integer) value; + break; +// case "margins": +// margins = MarginsExt.fromMap((Map) value); +// break; + case "mediaSize": + mediaSize = MediaSizeExt.fromMap((Map) value); + break; + case "colorMode": + colorMode = (Integer) value; + break; + case "duplexMode": + duplexMode = (Integer) value; + break; + case "resolution": + resolution = ResolutionExt.fromMap((Map) value); + break; + } + } + + return this; + } + + @NonNull + @Override + public Map toMap() { + Map settings = new HashMap<>(); + settings.put("handledByClient", handledByClient); + settings.put("jobName", jobName); + settings.put("orientation", orientation); +// settings.put("margins", margins != null ? margins.toMap() : null); + settings.put("mediaSize", mediaSize != null ? mediaSize.toMap() : null); + settings.put("colorMode", colorMode); + settings.put("duplexMode", duplexMode); + settings.put("resolution", resolution != null ? resolution.toMap() : null); + return settings; + } + + @NonNull + @Override + public Map getRealSettings(@NonNull PrintJobController printJobController) { + Map realSettings = toMap(); + return realSettings; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxySettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxySettings.java index 503d7592..bd2ae74b 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxySettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxySettings.java @@ -1,5 +1,6 @@ package com.pichillilorenzo.flutter_inappwebview.proxy; +import androidx.annotation.NonNull; import androidx.webkit.ProxyConfig; import com.pichillilorenzo.flutter_inappwebview.ISettings; @@ -17,8 +18,9 @@ public class ProxySettings implements ISettings { Boolean bypassSimpleHostnames = null; Boolean removeImplicitRules = null; + @NonNull @Override - public ProxySettings parse(Map settings) { + public ProxySettings parse(@NonNull Map settings) { for (Map.Entry pair : settings.entrySet()) { String key = pair.getKey(); Object value = pair.getValue(); @@ -55,6 +57,7 @@ public class ProxySettings implements ISettings { return this; } + @NonNull @Override public Map toMap() { List> proxyRuleMapList = new ArrayList<>(); @@ -70,8 +73,9 @@ public class ProxySettings implements ISettings { return settings; } + @NonNull @Override - public Map getRealSettings(ProxyConfig proxyConfig) { + public Map getRealSettings(@NonNull ProxyConfig proxyConfig) { Map realSettings = toMap(); List> proxyRuleMapList = new ArrayList<>(); List proxyRules = proxyConfig.getProxyRules(); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/pull_to_refresh/PullToRefreshSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/pull_to_refresh/PullToRefreshSettings.java index 7a256b9a..e7cad855 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/pull_to_refresh/PullToRefreshSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/pull_to_refresh/PullToRefreshSettings.java @@ -1,5 +1,6 @@ package com.pichillilorenzo.flutter_inappwebview.pull_to_refresh; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.pichillilorenzo.flutter_inappwebview.ISettings; @@ -22,7 +23,9 @@ public class PullToRefreshSettings implements ISettings { @Nullable public Integer size; - public PullToRefreshSettings parse(Map settings) { + @NonNull + @Override + public PullToRefreshSettings parse(@NonNull Map settings) { for (Map.Entry pair : settings.entrySet()) { String key = pair.getKey(); Object value = pair.getValue(); @@ -55,6 +58,7 @@ public class PullToRefreshSettings implements ISettings { return this; } + @NonNull public Map toMap() { Map settings = new HashMap<>(); settings.put("enabled", enabled); @@ -66,8 +70,9 @@ public class PullToRefreshSettings implements ISettings { return settings; } + @NonNull @Override - public Map getRealSettings(PullToRefreshLayout pullToRefreshLayout) { + public Map getRealSettings(@NonNull PullToRefreshLayout pullToRefreshLayout) { Map realSettings = toMap(); return realSettings; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/MarginsExt.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/MarginsExt.java new file mode 100644 index 00000000..1af1ce8b --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/MarginsExt.java @@ -0,0 +1,150 @@ +package com.pichillilorenzo.flutter_inappwebview.types; + +import android.os.Build; +import android.print.PrintAttributes; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.util.HashMap; +import java.util.Map; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class MarginsExt { + private double top; + private double right; + private double bottom; + private double left; + + public MarginsExt() {} + + public MarginsExt(double top, double right, double bottom, double left) { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + } + + @Nullable + public static MarginsExt fromMargins(@Nullable PrintAttributes.Margins margins) { + if (margins == null) { + return null; + } + MarginsExt marginsExt = new MarginsExt(); + marginsExt.top = milsToPixels(margins.getTopMils()); + marginsExt.right = milsToPixels(margins.getRightMils()); + marginsExt.bottom = milsToPixels(margins.getBottomMils()); + marginsExt.left = milsToPixels(margins.getLeftMils()); + return marginsExt; + } + + @Nullable + public static MarginsExt fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + return new MarginsExt( + (double) map.get("top"), + (double) map.get("right"), + (double) map.get("bottom"), + (double) map.get("left")); + } + + public PrintAttributes.Margins toMargins() { + return new PrintAttributes.Margins( + pixelsToMils(left), + pixelsToMils(top), + pixelsToMils(right), + pixelsToMils(bottom) + ); + } + + // from mils to pixels + private static double milsToPixels(int mils) { + return mils * 0.09600001209449; + } + + // from pixels to mils + private static int pixelsToMils(double pixels) { + return (int) Math.round(pixels * 10.416665354331); + } + + public Map toMap() { + Map obj = new HashMap<>(); + obj.put("top", top); + obj.put("right", right); + obj.put("bottom", bottom); + obj.put("left", left); + return obj; + } + + public double getTop() { + return top; + } + + public void setTop(double top) { + this.top = top; + } + + public double getRight() { + return right; + } + + public void setRight(double right) { + this.right = right; + } + + public double getBottom() { + return bottom; + } + + public void setBottom(double bottom) { + this.bottom = bottom; + } + + public double getLeft() { + return left; + } + + public void setLeft(double left) { + this.left = left; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MarginsExt that = (MarginsExt) o; + + if (Double.compare(that.top, top) != 0) return false; + if (Double.compare(that.right, right) != 0) return false; + if (Double.compare(that.bottom, bottom) != 0) return false; + return Double.compare(that.left, left) == 0; + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(top); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(right); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(bottom); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(left); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public String toString() { + return "MarginsExt{" + + "top=" + top + + ", right=" + right + + ", bottom=" + bottom + + ", left=" + left + + '}'; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/MediaSizeExt.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/MediaSizeExt.java new file mode 100644 index 00000000..552d71f6 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/MediaSizeExt.java @@ -0,0 +1,134 @@ +package com.pichillilorenzo.flutter_inappwebview.types; + +import android.os.Build; +import android.print.PrintAttributes; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.util.HashMap; +import java.util.Map; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class MediaSizeExt { + @NonNull + private String id; + @Nullable + private String label; + private int widthMils; + private int heightMils; + + public MediaSizeExt(@NonNull String id, @Nullable String label, int widthMils, int heightMils) { + this.id = id; + this.label = label; + this.widthMils = widthMils; + this.heightMils = heightMils; + } + + @Nullable + public static MediaSizeExt fromMediaSize(@Nullable PrintAttributes.MediaSize mediaSize) { + if (mediaSize == null) { + return null; + } + return new MediaSizeExt( + mediaSize.getId(), + null, + mediaSize.getHeightMils(), + mediaSize.getWidthMils() + ); + } + + @Nullable + public static MediaSizeExt fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + String id = (String) map.get("id"); + String label = (String) map.get("label"); + int widthMils = (int) map.get("widthMils"); + int heightMils = (int) map.get("heightMils"); + return new MediaSizeExt(id, label, widthMils, heightMils); + } + + public PrintAttributes.MediaSize toMediaSize() { + return new PrintAttributes.MediaSize( + id, "Custom", widthMils, heightMils + ); + } + + public Map toMap() { + Map obj = new HashMap<>(); + obj.put("id", id); + obj.put("label", label); + obj.put("heightMils", heightMils); + obj.put("widthMils", widthMils); + return obj; + } + + @NonNull + public String getId() { + return id; + } + + public void setId(@NonNull String id) { + this.id = id; + } + + @Nullable + public String getLabel() { + return label; + } + + public void setLabel(@Nullable String label) { + this.label = label; + } + + public int getWidthMils() { + return widthMils; + } + + public void setWidthMils(int widthMils) { + this.widthMils = widthMils; + } + + public int getHeightMils() { + return heightMils; + } + + public void setHeightMils(int heightMils) { + this.heightMils = heightMils; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MediaSizeExt that = (MediaSizeExt) o; + + if (widthMils != that.widthMils) return false; + if (heightMils != that.heightMils) return false; + if (!id.equals(that.id)) return false; + return label != null ? label.equals(that.label) : that.label == null; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + (label != null ? label.hashCode() : 0); + result = 31 * result + widthMils; + result = 31 * result + heightMils; + return result; + } + + @Override + public String toString() { + return "MediaSizeExt{" + + "id='" + id + '\'' + + ", label='" + label + '\'' + + ", widthMils=" + widthMils + + ", heightMils=" + heightMils + + '}'; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PrintAttributesExt.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PrintAttributesExt.java new file mode 100644 index 00000000..afbce7b2 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PrintAttributesExt.java @@ -0,0 +1,56 @@ +package com.pichillilorenzo.flutter_inappwebview.types; + +import android.os.Build; +import android.print.PrintAttributes; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.util.HashMap; +import java.util.Map; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class PrintAttributesExt { + private int colorMode; + @Nullable + private Integer duplex; + @Nullable + private Integer orientation; + @Nullable + private MediaSizeExt mediaSize; + @Nullable + private ResolutionExt resolution; + @Nullable + private MarginsExt margins; + + @Nullable + public static PrintAttributesExt fromPrintAttributes(@Nullable PrintAttributes attributes) { + if (attributes == null) { + return null; + } + PrintAttributesExt attributesExt = new PrintAttributesExt(); + attributesExt.colorMode = attributes.getColorMode(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + attributesExt.duplex = attributes.getDuplexMode(); + } + PrintAttributes.MediaSize mediaSize = attributes.getMediaSize(); + if (mediaSize != null) { + attributesExt.mediaSize = MediaSizeExt.fromMediaSize(mediaSize); + attributesExt.orientation = mediaSize.isPortrait() ? 0 : 1; + } + attributesExt.resolution = ResolutionExt.fromResolution(attributes.getResolution()); + attributesExt.margins = MarginsExt.fromMargins(attributes.getMinMargins()); + return attributesExt; + } + + public Map toMap() { + Map obj = new HashMap<>(); + obj.put("colorMode", colorMode); + obj.put("duplex", duplex); + obj.put("orientation", orientation); + obj.put("mediaSize", mediaSize != null ? mediaSize.toMap() : null); + obj.put("resolution", resolution != null ? resolution.toMap() : null); + obj.put("margins", margins != null ? margins.toMap() : null); + return obj; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PrintJobInfoExt.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PrintJobInfoExt.java new file mode 100644 index 00000000..f2aafe6e --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PrintJobInfoExt.java @@ -0,0 +1,55 @@ +package com.pichillilorenzo.flutter_inappwebview.types; + +import android.os.Build; +import android.print.PrintAttributes; +import android.print.PrintJobInfo; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.util.HashMap; +import java.util.Map; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class PrintJobInfoExt { + private int state; + private int copies; + @Nullable + private Integer numberOfPages; + private long creationTime; + @NonNull + private String label; + @Nullable + private String printerId; + @Nullable + private PrintAttributesExt attributes; + + @Nullable + public static PrintJobInfoExt fromPrintJobInfo(@Nullable PrintJobInfo info) { + if (info == null) { + return null; + } + PrintJobInfoExt printJobInfoExt = new PrintJobInfoExt(); + printJobInfoExt.state = info.getState(); + printJobInfoExt.copies = info.getCopies(); + printJobInfoExt.numberOfPages = info.getPages() != null ? info.getPages().length : null; + printJobInfoExt.creationTime = info.getCreationTime(); + printJobInfoExt.label = info.getLabel(); + printJobInfoExt.printerId = info.getPrinterId() != null ? info.getPrinterId().getLocalId() : null; + printJobInfoExt.attributes = PrintAttributesExt.fromPrintAttributes(info.getAttributes()); + return printJobInfoExt; + } + + public Map toMap() { + Map obj = new HashMap<>(); + obj.put("state", state); + obj.put("copies", copies); + obj.put("numberOfPages", numberOfPages); + obj.put("creationTime", creationTime); + obj.put("label", label); + obj.put("printerId", printerId); + obj.put("attributes", attributes != null ? attributes.toMap() : null); + return obj; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/ResolutionExt.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/ResolutionExt.java new file mode 100644 index 00000000..f4dffe8c --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/ResolutionExt.java @@ -0,0 +1,134 @@ +package com.pichillilorenzo.flutter_inappwebview.types; + +import android.os.Build; +import android.print.PrintAttributes; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.util.HashMap; +import java.util.Map; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class ResolutionExt { + @NonNull + private String id; + @NonNull + private String label; + private int verticalDpi; + private int horizontalDpi; + + public ResolutionExt(@NonNull String id, @NonNull String label, int verticalDpi, int horizontalDpi) { + this.id = id; + this.label = label; + this.verticalDpi = verticalDpi; + this.horizontalDpi = horizontalDpi; + } + + @Nullable + public static ResolutionExt fromResolution(@Nullable PrintAttributes.Resolution resolution) { + if (resolution == null) { + return null; + } + return new ResolutionExt( + resolution.getId(), + resolution.getLabel(), + resolution.getVerticalDpi(), + resolution.getHorizontalDpi() + ); + } + + @Nullable + public static ResolutionExt fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + String id = (String) map.get("id"); + String label = (String) map.get("label"); + int verticalDpi = (int) map.get("verticalDpi"); + int horizontalDpi = (int) map.get("horizontalDpi"); + return new ResolutionExt(id, label, verticalDpi, horizontalDpi); + } + + public PrintAttributes.Resolution toResolution() { + return new PrintAttributes.Resolution( + id, label, horizontalDpi, verticalDpi + ); + } + + public Map toMap() { + Map obj = new HashMap<>(); + obj.put("id", id); + obj.put("label", label); + obj.put("verticalDpi", verticalDpi); + obj.put("horizontalDpi", horizontalDpi); + return obj; + } + + @NonNull + public String getId() { + return id; + } + + public void setId(@NonNull String id) { + this.id = id; + } + + @NonNull + public String getLabel() { + return label; + } + + public void setLabel(@NonNull String label) { + this.label = label; + } + + public int getVerticalDpi() { + return verticalDpi; + } + + public void setVerticalDpi(int verticalDpi) { + this.verticalDpi = verticalDpi; + } + + public int getHorizontalDpi() { + return horizontalDpi; + } + + public void setHorizontalDpi(int horizontalDpi) { + this.horizontalDpi = horizontalDpi; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ResolutionExt that = (ResolutionExt) o; + + if (verticalDpi != that.verticalDpi) return false; + if (horizontalDpi != that.horizontalDpi) return false; + if (!id.equals(that.id)) return false; + return label.equals(that.label); + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + label.hashCode(); + result = 31 * result + verticalDpi; + result = 31 * result + horizontalDpi; + return result; + } + + @Override + public String toString() { + return "ResolutionExt{" + + "id='" + id + '\'' + + ", label='" + label + '\'' + + ", verticalDpi=" + verticalDpi + + ", horizontalDpi=" + horizontalDpi + + '}'; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/ContextMenuSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/ContextMenuSettings.java index 46db0b16..825513f1 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/ContextMenuSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/ContextMenuSettings.java @@ -1,5 +1,7 @@ package com.pichillilorenzo.flutter_inappwebview.webview; +import androidx.annotation.NonNull; + import com.pichillilorenzo.flutter_inappwebview.ISettings; import java.util.HashMap; @@ -10,7 +12,9 @@ public class ContextMenuSettings implements ISettings { public Boolean hideDefaultSystemContextMenuItems = false; - public ContextMenuSettings parse(Map options) { + @NonNull + @Override + public ContextMenuSettings parse(@NonNull Map options) { for (Map.Entry pair : options.entrySet()) { String key = pair.getKey(); Object value = pair.getValue(); @@ -28,14 +32,16 @@ public class ContextMenuSettings implements ISettings { return this; } + @NonNull public Map toMap() { Map options = new HashMap<>(); options.put("hideDefaultSystemContextMenuItems", hideDefaultSystemContextMenuItems); return options; } + @NonNull @Override - public Map getRealSettings(Object obj) { + public Map getRealSettings(@NonNull Object obj) { Map realOptions = toMap(); return realOptions; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/InAppWebViewInterface.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/InAppWebViewInterface.java index 877ac0b5..5c55642d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/InAppWebViewInterface.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/InAppWebViewInterface.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserDelegate; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobSettings; import com.pichillilorenzo.flutter_inappwebview.types.ContentWorld; import com.pichillilorenzo.flutter_inappwebview.types.HitTestResult; import com.pichillilorenzo.flutter_inappwebview.types.URLRequest; @@ -64,7 +65,8 @@ public interface InAppWebViewInterface { void onResume(); void pauseTimers(); void resumeTimers(); - void printCurrentPage(); + @Nullable + String printCurrentPage(@Nullable PrintJobSettings settings); int getContentHeight(); void getContentHeight(ValueCallback callback); void zoomBy(float zoomFactor); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/JavaScriptBridgeInterface.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/JavaScriptBridgeInterface.java index 586d622e..1eb0cede 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/JavaScriptBridgeInterface.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/JavaScriptBridgeInterface.java @@ -6,11 +6,16 @@ import android.util.Log; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobController; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobSettings; import com.pichillilorenzo.flutter_inappwebview.webview.WebViewChannelDelegate; import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.InAppWebView; import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS; +import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.InAppWebViewChromeClient; import org.json.JSONArray; import org.json.JSONException; @@ -58,8 +63,33 @@ public class JavaScriptBridgeInterface { return; } - if (handlerName.equals("onPrint") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - inAppWebView.printCurrentPage(); + if (handlerName.equals("onPrintRequest") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + PrintJobSettings settings = new PrintJobSettings(); + settings.handledByClient = true; + final String printJobId = inAppWebView.printCurrentPage(settings); + if (inAppWebView != null && inAppWebView.channelDelegate != null) { + inAppWebView.channelDelegate.onPrintRequest(inAppWebView.getUrl(), printJobId, new WebViewChannelDelegate.PrintRequestCallback() { + @Override + public boolean nonNullSuccess(@NonNull Boolean handledByClient) { + return !handledByClient; + } + + @Override + public void defaultBehaviour(@Nullable Boolean handledByClient) { + PrintJobController printJobController = PrintJobManager.jobs.get(printJobId); + if (printJobController != null) { + printJobController.disposeNoCancel(); + } + } + + @Override + public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : "")); + defaultBehaviour(null); + } + }); + } + return; } else if (handlerName.equals("callAsyncJavaScript")) { try { JSONArray arguments = new JSONArray(args); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/WebViewChannelDelegate.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/WebViewChannelDelegate.java index 1285ec00..cd4744a5 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/WebViewChannelDelegate.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/WebViewChannelDelegate.java @@ -15,6 +15,7 @@ import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview.Util; import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserActivity; import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserSettings; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobSettings; import com.pichillilorenzo.flutter_inappwebview.types.BaseCallbackResultImpl; import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl; import com.pichillilorenzo.flutter_inappwebview.types.ClientCertChallenge; @@ -344,9 +345,15 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl { break; case "printCurrentPage": if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - webView.printCurrentPage(); + PrintJobSettings settings = new PrintJobSettings(); + Map settingsMap = (Map) call.argument("settings"); + if (settingsMap != null) { + settings.parse(settingsMap); + } + result.success(webView.printCurrentPage(settings)); + } else { + result.success(null); } - result.success(true); break; case "getContentHeight": if (webView instanceof InAppWebView) { @@ -1230,6 +1237,26 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl { channel.invokeMethod("onCallJsHandler", obj, callback); } + public static class PrintRequestCallback extends BaseCallbackResultImpl { + @Nullable + @Override + public Boolean decodeResult(@Nullable Object obj) { + return (obj instanceof Boolean) && (boolean) obj; + } + } + + public void onPrintRequest(String url, String printJobId, @NonNull PrintRequestCallback callback) { + MethodChannel channel = getChannel(); + if (channel == null) { + callback.defaultBehaviour(null); + return; + } + Map obj = new HashMap<>(); + obj.put("url", url); + obj.put("printJobId", printJobId); + channel.invokeMethod("onPrintRequest", obj, callback); + } + @Override public void dispose() { super.dispose(); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java index 4082226b..626b27fc 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java @@ -55,6 +55,9 @@ import androidx.webkit.WebViewCompat; import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobController; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager; +import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobSettings; import com.pichillilorenzo.flutter_inappwebview.webview.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.Util; @@ -1251,25 +1254,75 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie webSettings.setBuiltInZoomControls(enabled); } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void printCurrentPage() { + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + @Nullable + public String printCurrentPage(@Nullable PrintJobSettings settings) { if (plugin != null && plugin.activity != null) { // Get a PrintManager instance PrintManager printManager = (PrintManager) plugin.activity.getSystemService(Context.PRINT_SERVICE); if (printManager != null) { - String jobName = getTitle() + " Document"; + PrintAttributes.Builder builder = new PrintAttributes.Builder(); + + String jobName = (getTitle() != null ? getTitle() : getUrl()) + " Document"; + + if (settings != null) { + if (settings.jobName != null && !settings.jobName.isEmpty()) { + jobName = settings.jobName; + } + if (settings.orientation != null) { + int orientation = settings.orientation; + switch (orientation) { + case 0: + // PORTRAIT + builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT); + break; + case 1: + // LANDSCAPE + builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE); + break; + } + } +// if (settings.margins != null) { +// // for some reason, Android doesn't set the margins +// builder.setMinMargins(settings.margins.toMargins()); +// } + if (settings.mediaSize != null) { + builder.setMediaSize(settings.mediaSize.toMediaSize()); + } + if (settings.colorMode != null) { + builder.setColorMode(settings.colorMode); + } + if (settings.duplexMode != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + builder.setDuplexMode(settings.duplexMode); + } + if (settings.resolution != null) { + builder.setResolution(settings.resolution.toResolution()); + } + } // Get a printCurrentPage adapter instance - PrintDocumentAdapter printAdapter = createPrintDocumentAdapter(jobName); + PrintDocumentAdapter printAdapter; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + printAdapter = createPrintDocumentAdapter(jobName); + } else { + printAdapter = createPrintDocumentAdapter(); + } // Create a printCurrentPage job with name and adapter instance - printManager.print(jobName, printAdapter, - new PrintAttributes.Builder().build()); + android.print.PrintJob job = printManager.print(jobName, printAdapter, builder.build()); + + if (settings != null && settings.handledByClient) { + String id = UUID.randomUUID().toString(); + PrintJobController printJobController = new PrintJobController(id, job, settings, plugin); + PrintJobManager.jobs.put(printJobController.id, printJobController); + return id; + } } else { Log.e(LOG_TAG, "No PrintManager available"); } } + return null; } @Override diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewSettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewSettings.java index 56057ec3..e82394bc 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewSettings.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewSettings.java @@ -4,6 +4,7 @@ import android.os.Build; import android.view.View; import android.webkit.WebSettings; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.webkit.WebSettingsCompat; import androidx.webkit.WebViewFeature; @@ -112,8 +113,9 @@ public class InAppWebViewSettings implements ISettings { @Nullable public String horizontalScrollbarTrackColor; + @NonNull @Override - public InAppWebViewSettings parse(Map settings) { + public InAppWebViewSettings parse(@NonNull Map settings) { for (Map.Entry pair : settings.entrySet()) { String key = pair.getKey(); Object value = pair.getValue(); @@ -374,6 +376,7 @@ public class InAppWebViewSettings implements ISettings { return this; } + @NonNull @Override public Map toMap() { Map settings = new HashMap<>(); @@ -462,8 +465,9 @@ public class InAppWebViewSettings implements ISettings { return settings; } + @NonNull @Override - public Map getRealSettings(InAppWebViewInterface inAppWebView) { + public Map getRealSettings(@NonNull InAppWebViewInterface inAppWebView) { Map realSettings = toMap(); if (inAppWebView instanceof InAppWebView) { InAppWebView webView = (InAppWebView) inAppWebView; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageChannel.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageChannel.java index ce465be5..075ee37b 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageChannel.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageChannel.java @@ -27,7 +27,8 @@ import io.flutter.plugin.common.MethodChannel; public class WebMessageChannel implements Disposable { protected static final String LOG_TAG = "WebMessageChannel"; public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_"; - + + @NonNull public String id; @Nullable public WebMessageChannelChannelDelegate channelDelegate; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageChannelChannelDelegate.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageChannelChannelDelegate.java index 630f50c6..c56fca49 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageChannelChannelDelegate.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageChannelChannelDelegate.java @@ -30,7 +30,7 @@ public class WebMessageChannelChannelDelegate extends ChannelDelegateImpl { final Integer index = (Integer) call.argument("index"); webMessageChannel.setWebMessageCallbackForInAppWebView(index, result); } else { - result.success(true); + result.success(false); } break; case "postMessage": @@ -39,7 +39,7 @@ public class WebMessageChannelChannelDelegate extends ChannelDelegateImpl { Map message = (Map) call.argument("message"); webMessageChannel.postMessageForInAppWebView(index, message, result); } else { - result.success(true); + result.success(false); } break; case "close": @@ -47,7 +47,7 @@ public class WebMessageChannelChannelDelegate extends ChannelDelegateImpl { Integer index = (Integer) call.argument("index"); webMessageChannel.closeForInAppWebView(index, result); } else { - result.success(true); + result.success(false); } break; default: diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageListenerChannelDelegate.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageListenerChannelDelegate.java index d37b4a95..9b2155eb 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageListenerChannelDelegate.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageListenerChannelDelegate.java @@ -31,7 +31,7 @@ public class WebMessageListenerChannelDelegate extends ChannelDelegateImpl { String message = (String) call.argument("message"); webMessageListener.postMessageForInAppWebView(message, result); } else { - result.success(true); + result.success(false); } break; default: diff --git a/example/integration_test/in_app_webview/on_print.dart b/example/integration_test/in_app_webview/on_print.dart index 0134a549..b594ada5 100644 --- a/example/integration_test/in_app_webview/on_print.dart +++ b/example/integration_test/in_app_webview/on_print.dart @@ -29,8 +29,9 @@ void onPrint() { onLoadStop: (controller, url) async { await controller.evaluateJavascript(source: "window.print();"); }, - onPrint: (controller, url) { + onPrintRequest: (controller, url, printJob) async { onPrintCompleter.complete(url?.toString()); + return false; }, ), ), diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index c172bedd..f2e08d87 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -114,7 +114,7 @@ class _InAppWebViewExampleScreenState extends State { InAppWebView( key: webViewKey, initialUrlRequest: - URLRequest(url: Uri.parse('https://flutter.dev/')), + URLRequest(url: Uri.parse('https://github.com/flutter/')), // initialUrlRequest: // URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')), // initialFile: "assets/index.html", diff --git a/ios/Classes/ISettings.swift b/ios/Classes/ISettings.swift index 7e12fdd5..3c536486 100755 --- a/ios/Classes/ISettings.swift +++ b/ios/Classes/ISettings.swift @@ -14,10 +14,14 @@ public class ISettings: NSObject { super.init() } - func parse(settings: [String: Any?]) -> ISettings { + func parse(settings: [String: Any?]) -> ISettings { for (key, value) in settings { - if !(value is NSNull), value != nil, self.responds(to: Selector(key)) { - self.setValue(value, forKey: key) + if !(value is NSNull), value != nil { + if self.responds(to: Selector(key)) { + self.setValue(value, forKey: key) + } else if self.responds(to: Selector("_" + key)) { + self.setValue(value, forKey: "_" + key) + } } } return self @@ -25,15 +29,15 @@ public class ISettings: NSObject { func toMap() -> [String: Any?] { var settings: [String: Any?] = [:] - var counts = UInt32(); - let properties = class_copyPropertyList(object_getClass(self), &counts); + var counts = UInt32() + let properties = class_copyPropertyList(object_getClass(self), &counts) for i in 0.. Void)?) { + public func printCurrentPage(settings: PrintJobSettings? = nil, + completionHandler: UIPrintInteractionController.CompletionHandler? = nil) -> String? { + var printJobId: String? = nil + if let settings = settings, settings.handledByClient { + printJobId = NSUUID().uuidString + } + let printController = UIPrintInteractionController.shared let printFormatter = self.viewPrintFormatter() + if let settings = settings { + if let margins = settings.margins { + printFormatter.perPageContentInsets = margins + } + if let maximumContentHeight = settings.maximumContentHeight { + printFormatter.maximumContentHeight = maximumContentHeight + } + if let maximumContentWidth = settings.maximumContentWidth { + printFormatter.maximumContentWidth = maximumContentWidth + } + } printController.printFormatter = printFormatter - let completionHandler: UIPrintInteractionController.CompletionHandler = { (printController, completed, error) in - if !completed { - if let e = error { - print("[PRINT] Failed: \(e.localizedDescription)") - } else { - print("[PRINT] Canceled") + printController.printInfo = UIPrintInfo(dictionary: nil) + if let printInfo = printController.printInfo { + printInfo.jobName = settings?.jobName ?? (title ?? url?.absoluteString ?? "") + " Document" + if let settings = settings { + if let orientationValue = settings.orientation, + let orientation = UIPrintInfo.Orientation.init(rawValue: orientationValue) { + printInfo.orientation = orientation + } + if let duplexModeValue = settings.duplexMode, + let duplexMode = UIPrintInfo.Duplex.init(rawValue: duplexModeValue) { + printInfo.duplex = duplexMode + } + if let outputTypeValue = settings.outputType, + let outputType = UIPrintInfo.OutputType.init(rawValue: outputTypeValue) { + printInfo.outputType = outputType } - } - if let callback = printCompletionHandler { - callback(completed, error) } } - printController.present(animated: true, completionHandler: completionHandler) + // initialize print renderer and set its formatter + let printRenderer = CustomUIPrintPageRenderer(numberOfPage: settings?.numberOfPages, + forceRenderingQuality: settings?.forceRenderingQuality) + printRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0) + if let settings = settings { + if let footerHeight = settings.footerHeight { + printRenderer.footerHeight = footerHeight + } + if let headerHeight = settings.headerHeight { + printRenderer.headerHeight = headerHeight + } + } + printController.printPageRenderer = printRenderer + + if let settings = settings { + printController.showsNumberOfCopies = settings.showsNumberOfCopies + printController.showsPaperSelectionForLoadedPapers = settings.showsPaperSelectionForLoadedPapers + if #available(iOS 15.0, *) { + printController.showsPaperOrientation = settings.showsPaperOrientation + } + } + + let animated = settings?.animated ?? true + if let id = printJobId { + let printJob = PrintJobController(id: id, job: printController, settings: settings) + PrintJobManager.jobs[id] = printJob + printJob.present(animated: animated, completionHandler: completionHandler) + } else { + printController.present(animated: animated, completionHandler: completionHandler) + } + + return printJobId } public func getContentHeight() -> Int64 { diff --git a/ios/Classes/InAppWebView/InAppWebViewSettings.swift b/ios/Classes/InAppWebView/InAppWebViewSettings.swift index d3597bb5..7bda38bd 100755 --- a/ios/Classes/InAppWebView/InAppWebViewSettings.swift +++ b/ios/Classes/InAppWebView/InAppWebViewSettings.swift @@ -46,8 +46,8 @@ public class InAppWebViewSettings: ISettings { var ignoresViewportScaleLimits = false var allowsInlineMediaPlayback = false var allowsPictureInPictureMediaPlayback = true - var isFraudulentWebsiteWarningEnabled = true; - var selectionGranularity = 0; + var isFraudulentWebsiteWarningEnabled = true + var selectionGranularity = 0 var dataDetectorTypes: [String] = ["NONE"] // WKDataDetectorTypeNone var preferredContentMode = 0 var sharedCookiesEnabled = false diff --git a/ios/Classes/InAppWebView/WebViewChannelDelegate.swift b/ios/Classes/InAppWebView/WebViewChannelDelegate.swift index b1cb5fa3..78f83b89 100644 --- a/ios/Classes/InAppWebView/WebViewChannelDelegate.swift +++ b/ios/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -273,16 +273,13 @@ public class WebViewChannelDelegate : ChannelDelegate { break case "printCurrentPage": if let webView = webView { - webView.printCurrentPage(printCompletionHandler: {(completed, error) in - if !completed, let err = error { - print(err.localizedDescription) - result(false) - return - } - result(true) - }) + let settings = PrintJobSettings() + if let settingsMap = arguments!["settings"] as? [String: Any?] { + let _ = settings.parse(settings: settingsMap) + } + result(webView.printCurrentPage(settings: settings)) } else { - result(false) + result(nil) } break case "getContentHeight": @@ -1054,6 +1051,27 @@ public class WebViewChannelDelegate : ChannelDelegate { channel?.invokeMethod("onMicrophoneCaptureStateChanged", arguments: arguments) } + public class PrintRequestCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return obj is Bool && obj as! Bool + } + } + } + + public func onPrintRequest(url: URL?, printJobId: String?, callback: PrintRequestCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + let arguments = [ + "url": url?.absoluteString, + "printJobId": printJobId, + ] + channel.invokeMethod("onPrintRequest", arguments: arguments, callback: callback) + } + public override func dispose() { super.dispose() webView = nil diff --git a/ios/Classes/PluginScriptsJS/PrintJS.swift b/ios/Classes/PluginScriptsJS/PrintJS.swift index 56a7f304..5ac12691 100644 --- a/ios/Classes/PluginScriptsJS/PrintJS.swift +++ b/ios/Classes/PluginScriptsJS/PrintJS.swift @@ -19,6 +19,6 @@ let PRINT_JS_PLUGIN_SCRIPT = PluginScript( let PRINT_JS_SOURCE = """ window.print = function() { - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrint", window.location.href); + window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrintRequest", window.location.href); } """ diff --git a/ios/Classes/PrintJob/CustomUIPrintPageRenderer.swift b/ios/Classes/PrintJob/CustomUIPrintPageRenderer.swift new file mode 100644 index 00000000..ae1a32ee --- /dev/null +++ b/ios/Classes/PrintJob/CustomUIPrintPageRenderer.swift @@ -0,0 +1,34 @@ +// +// CustomUIPrintPageRenderer.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 10/05/22. +// + +import Foundation + +public class CustomUIPrintPageRenderer : UIPrintPageRenderer { + private var _numberOfPages: Int? + private var forceRenderingQuality: Int? + + public init(numberOfPage: Int? = nil, forceRenderingQuality: Int? = nil) { + super.init() + self._numberOfPages = numberOfPage + self.forceRenderingQuality = forceRenderingQuality + } + + open override var numberOfPages: Int { + get { + return _numberOfPages ?? super.numberOfPages + } + } + + @available(iOS 14.5, *) + open override func currentRenderingQuality(forRequested requestedRenderingQuality: UIPrintRenderingQuality) -> UIPrintRenderingQuality { + if let forceRenderingQuality = forceRenderingQuality, + let quality = UIPrintRenderingQuality.init(rawValue: forceRenderingQuality) { + return quality + } + return super.currentRenderingQuality(forRequested: requestedRenderingQuality) + } +} diff --git a/ios/Classes/PrintJob/PrintAttributes.swift b/ios/Classes/PrintJob/PrintAttributes.swift new file mode 100644 index 00000000..4c0b15ce --- /dev/null +++ b/ios/Classes/PrintJob/PrintAttributes.swift @@ -0,0 +1,56 @@ +// +// PrintAttributes.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 10/05/22. +// + +import Foundation + +public class PrintAttributes : NSObject { + var orientation: UIPrintInfo.Orientation? + var duplex: UIPrintInfo.Duplex? + var outputType: UIPrintInfo.OutputType? + var margins: UIEdgeInsets? + var footerHeight: Double? + var headerHeight: Double? + var paperRect: CGRect? + var printableRect: CGRect? + var maximumContentHeight: Double? + var maximumContentWidth: Double? + + public init(fromPrintJobController: PrintJobController) { + super.init() + if let printPageRenderer = fromPrintJobController.printPageRenderer { + footerHeight = printPageRenderer.footerHeight + headerHeight = printPageRenderer.headerHeight + paperRect = printPageRenderer.paperRect + printableRect = printPageRenderer.printableRect + } + if let printFormatter = fromPrintJobController.printFormatter { + maximumContentHeight = printFormatter.maximumContentHeight + maximumContentWidth = printFormatter.maximumContentWidth + margins = printFormatter.perPageContentInsets + } + if let job = fromPrintJobController.job, let printInfo = job.printInfo { + orientation = printInfo.orientation + duplex = printInfo.duplex + outputType = printInfo.outputType + } + } + + public func toMap () -> [String:Any?] { + return [ + "footerHeight": footerHeight, + "headerHeight": headerHeight, + "paperRect": paperRect?.toMap(), + "printableRect": printableRect?.toMap(), + "margins": margins?.toMap(), + "maximumContentHeight": maximumContentHeight, + "maximumContentWidth": maximumContentWidth, + "orientation": orientation?.rawValue, + "duplex": duplex?.rawValue, + "outputType": outputType?.rawValue + ] + } +} diff --git a/ios/Classes/PrintJob/PrintJobChannelDelegate.swift b/ios/Classes/PrintJob/PrintJobChannelDelegate.swift new file mode 100644 index 00000000..7063bd81 --- /dev/null +++ b/ios/Classes/PrintJob/PrintJobChannelDelegate.swift @@ -0,0 +1,68 @@ +// +// PrintJobChannelDelegate.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 09/05/22. +// + +import Foundation + +public class PrintJobChannelDelegate : ChannelDelegate { + private weak var printJobController: PrintJobController? + + public init(printJobController: PrintJobController, channel: FlutterMethodChannel) { + super.init(channel: channel) + self.printJobController = printJobController + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? NSDictionary + + switch call.method { + case "dismiss": + if let job = printJobController?.job { + let animated = arguments!["animated"] as! Bool + job.dismiss(animated: animated) + result(true) + } else { + result(false) + } + break + case "getInfo": + if let printJobController = printJobController { + result(printJobController.getInfo()?.toMap()) + } else { + result(false) + } + break + case "dispose": + if let printJobController = printJobController { + printJobController.dispose() + result(true) + } else { + result(false) + } + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public func onComplete(completed: Bool, error: Error?) { + let arguments: [String: Any?] = [ + "completed": completed, + "error": error?.localizedDescription + ] + channel?.invokeMethod("onComplete", arguments: arguments) + } + + public override func dispose() { + super.dispose() + printJobController = nil + } + + deinit { + dispose() + } +} diff --git a/ios/Classes/PrintJob/PrintJobController.swift b/ios/Classes/PrintJob/PrintJobController.swift new file mode 100644 index 00000000..bf28d131 --- /dev/null +++ b/ios/Classes/PrintJob/PrintJobController.swift @@ -0,0 +1,96 @@ +// +// PrintJob.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 09/05/22. +// + +import Foundation + +public enum PrintJobState: Int { + case created = 1 + case started = 3 + case completed = 5 + case failed = 6 + case canceled = 7 +} + +public class PrintJobController : NSObject, Disposable, UIPrintInteractionControllerDelegate { + static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_" + var id: String + var job: UIPrintInteractionController? + var settings: PrintJobSettings? + var printFormatter: UIPrintFormatter? + var printPageRenderer: UIPrintPageRenderer? + var channelDelegate: PrintJobChannelDelegate? + var state = PrintJobState.created + var creationTime = Int64(Date().timeIntervalSince1970 * 1000) + + public init(id: String, job: UIPrintInteractionController? = nil, settings: PrintJobSettings? = nil) { + self.id = id + super.init() + self.job = job + self.settings = settings + self.printFormatter = job?.printFormatter + self.printPageRenderer = job?.printPageRenderer + self.job?.delegate = self + let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) + self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) + } + + public func printInteractionControllerWillStartJob(_ printInteractionController: UIPrintInteractionController) { + state = .started + } + + public func present(animated: Bool, completionHandler: UIPrintInteractionController.CompletionHandler? = nil) { + guard let job = job else { + return + } + + job.present(animated: animated, completionHandler: { [weak self] (printController, completed, error) in + if !completed { + if let _ = error { + self?.state = .failed + } else { + self?.state = .canceled + } + } else { + self?.state = .completed + } + self?.channelDelegate?.onComplete(completed: completed, error: error) + if let completionHandler = completionHandler { + completionHandler(printController, completed, error) + } + }) + } + + public func getInfo() -> PrintJobInfo? { + guard let _ = job else { + return nil + } + + return PrintJobInfo.init(fromPrintJobController: self) + } + + public func disposeNoDismiss() { + channelDelegate?.dispose() + channelDelegate = nil + printFormatter = nil + printPageRenderer = nil + job?.delegate = nil + job = nil + PrintJobManager.jobs[id] = nil + } + + public func dispose() { + channelDelegate?.dispose() + channelDelegate = nil + printFormatter = nil + printPageRenderer = nil + job?.delegate = nil + job?.dismiss(animated: false) + job = nil + PrintJobManager.jobs[id] = nil + } +} diff --git a/ios/Classes/PrintJob/PrintJobInfo.swift b/ios/Classes/PrintJob/PrintJobInfo.swift new file mode 100644 index 00000000..9eb8aba1 --- /dev/null +++ b/ios/Classes/PrintJob/PrintJobInfo.swift @@ -0,0 +1,43 @@ +// +// PrintJobInfo.swift +// flutter_downloader +// +// Created by Lorenzo Pichilli on 10/05/22. +// + +import Foundation + +public class PrintJobInfo : NSObject { + var state: PrintJobState + var attributes: PrintAttributes + var creationTime: Int64 + var numberOfPages: Int? + var label: String? + var printerId: String? + + public init(fromPrintJobController: PrintJobController) { + state = fromPrintJobController.state + creationTime = fromPrintJobController.creationTime + attributes = PrintAttributes.init(fromPrintJobController: fromPrintJobController) + super.init() + if let printPageRenderer = fromPrintJobController.printPageRenderer { + numberOfPages = printPageRenderer.numberOfPages + } + if let job = fromPrintJobController.job, let printInfo = job.printInfo { + label = printInfo.jobName + printerId = printInfo.printerID + } + } + + public func toMap () -> [String:Any?] { + return [ + "state": state.rawValue, + "attributes": attributes.toMap(), + "copies": nil, + "numberOfPages": numberOfPages, + "creationTime": creationTime, + "label": label, + "printerId": printerId + ] + } +} diff --git a/ios/Classes/PrintJob/PrintJobManager.swift b/ios/Classes/PrintJob/PrintJobManager.swift new file mode 100644 index 00000000..7148027a --- /dev/null +++ b/ios/Classes/PrintJob/PrintJobManager.swift @@ -0,0 +1,28 @@ +// +// PrintJobManager.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 09/05/22. +// + +import Foundation + +public class PrintJobManager: NSObject, Disposable { + static var jobs: [String: PrintJobController?] = [:] + + public override init() { + super.init() + } + + public func dispose() { + let jobs = PrintJobManager.jobs.values + jobs.forEach { (job: PrintJobController?) in + job?.dispose() + } + PrintJobManager.jobs.removeAll() + } + + deinit { + dispose() + } +} diff --git a/ios/Classes/PrintJob/PrintJobSettings.swift b/ios/Classes/PrintJob/PrintJobSettings.swift new file mode 100644 index 00000000..0049c421 --- /dev/null +++ b/ios/Classes/PrintJob/PrintJobSettings.swift @@ -0,0 +1,154 @@ +// +// PrintJobSettings.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 09/05/22. +// + +import Foundation + +@objcMembers +public class PrintJobSettings: ISettings { + + public var handledByClient = false + public var jobName: String? + public var animated = true + public var _orientation: NSNumber? + public var orientation: Int? { + get { + return _orientation?.intValue + } + set { + if let newValue = newValue { + _orientation = NSNumber.init(value: newValue) + } else { + _orientation = nil + } + } + } + public var _numberOfPages: NSNumber? + public var numberOfPages: Int? { + get { + return _numberOfPages?.intValue + } + set { + if let newValue = newValue { + _numberOfPages = NSNumber.init(value: newValue) + } else { + _numberOfPages = nil + } + } + } + public var _forceRenderingQuality: NSNumber? + public var forceRenderingQuality: Int? { + get { + return _forceRenderingQuality?.intValue + } + set { + if let newValue = newValue { + _forceRenderingQuality = NSNumber.init(value: newValue) + } else { + _forceRenderingQuality = nil + } + } + } + public var margins: UIEdgeInsets? + public var _duplexMode: NSNumber? + public var duplexMode: Int? { + get { + return _duplexMode?.intValue + } + set { + if let newValue = newValue { + _duplexMode = NSNumber.init(value: newValue) + } else { + _duplexMode = nil + } + } + } + public var _outputType: NSNumber? + public var outputType: Int? { + get { + return _outputType?.intValue + } + set { + if let newValue = newValue { + _outputType = NSNumber.init(value: newValue) + } else { + _outputType = nil + } + } + } + public var showsNumberOfCopies = true + public var showsPaperSelectionForLoadedPapers = false + public var showsPaperOrientation = true + public var _maximumContentHeight: NSNumber? + public var maximumContentHeight: Double? { + get { + return _maximumContentHeight?.doubleValue + } + set { + if let newValue = newValue { + _maximumContentHeight = NSNumber.init(value: newValue) + } else { + _maximumContentHeight = nil + } + } + } + public var _maximumContentWidth: NSNumber? + public var maximumContentWidth: Double? { + get { + return _maximumContentWidth?.doubleValue + } + set { + if let newValue = newValue { + _maximumContentWidth = NSNumber.init(value: newValue) + } else { + _maximumContentWidth = nil + } + } + } + public var _footerHeight: NSNumber? + public var footerHeight: Double? { + get { + return _footerHeight?.doubleValue + } + set { + if let newValue = newValue { + _footerHeight = NSNumber.init(value: newValue) + } else { + _footerHeight = nil + } + } + } + public var _headerHeight: NSNumber? + public var headerHeight: Double? { + get { + return _headerHeight?.doubleValue + } + set { + if let newValue = newValue { + _headerHeight = NSNumber.init(value: newValue) + } else { + _headerHeight = nil + } + } + } + + override init(){ + super.init() + } + + override func parse(settings: [String: Any?]) -> PrintJobSettings { + let _ = super.parse(settings: settings) + if let marginsMap = settings["margins"] as? [String : Double] { + margins = UIEdgeInsets.fromMap(map: marginsMap) + } + return self + } + + override func getRealSettings(obj: PrintJobController?) -> [String: Any?] { + var realOptions: [String: Any?] = toMap() + return realOptions + } +} diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift index cb0ca7ac..03e97094 100755 --- a/ios/Classes/SwiftFlutterPlugin.swift +++ b/ios/Classes/SwiftFlutterPlugin.swift @@ -35,6 +35,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { var headlessInAppWebViewManager: HeadlessInAppWebViewManager? var chromeSafariBrowserManager: ChromeSafariBrowserManager? var webAuthenticationSessionManager: WebAuthenticationSessionManager? + var printJobManager: PrintJobManager? var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var safariViewControllers: [String: Any?] = [:] @@ -58,6 +59,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { myWebStorageManager = MyWebStorageManager(registrar: registrar) } webAuthenticationSessionManager = WebAuthenticationSessionManager(registrar: registrar) + printJobManager = PrintJobManager() } public static func register(with registrar: FlutterPluginRegistrar) { @@ -87,5 +89,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } webAuthenticationSessionManager?.dispose() webAuthenticationSessionManager = nil + printJobManager?.dispose() + printJobManager = nil } } diff --git a/ios/Classes/Types/CGRect.swift b/ios/Classes/Types/CGRect.swift new file mode 100644 index 00000000..61cb7627 --- /dev/null +++ b/ios/Classes/Types/CGRect.swift @@ -0,0 +1,23 @@ +// +// CGRect.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 10/05/22. +// + +import Foundation + +extension CGRect { + public static func fromMap(map: [String: Double]) -> CGRect { + return CGRect(x: map["x"]!, y: map["y"]!, width: map["width"]!, height: map["height"]!) + } + + public func toMap () -> [String:Any?] { + return [ + "x": minX, + "y": minY, + "width": width, + "height": height + ] + } +} diff --git a/ios/Classes/Types/UIEdgeInsets.swift b/ios/Classes/Types/UIEdgeInsets.swift new file mode 100644 index 00000000..5bf3130a --- /dev/null +++ b/ios/Classes/Types/UIEdgeInsets.swift @@ -0,0 +1,23 @@ +// +// UIEdgeInsets.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 11/05/22. +// + +import Foundation + +extension UIEdgeInsets { + public static func fromMap(map: [String: Double]) -> UIEdgeInsets { + return UIEdgeInsets.init(top: map["top"]!, left: map["left"]!, bottom: map["bottom"]!, right: map["right"]!) + } + + public func toMap () -> [String:Any?] { + return [ + "top": top, + "right": self.right, + "bottom": bottom, + "left": self.left + ] + } +} diff --git a/lib/assets/web/web_support.js b/lib/assets/web/web_support.js index f46324ba..7e58be1a 100644 --- a/lib/assets/web/web_support.js +++ b/lib/assets/web/web_support.js @@ -124,7 +124,7 @@ window.flutter_inappwebview = { } catch (e) { console.log(e); } - window.flutter_inappwebview.nativeCommunication('onPrint', viewId, [iframeUrl]); + window.flutter_inappwebview.nativeCommunication('onPrintRequest', viewId, [iframeUrl]); originalPrint.call(iframe.contentWindow); }; diff --git a/lib/src/in_app_browser/in_app_browser.dart b/lib/src/in_app_browser/in_app_browser.dart index 4051a3e7..3f1f8371 100755 --- a/lib/src/in_app_browser/in_app_browser.dart +++ b/lib/src/in_app_browser/in_app_browser.dart @@ -12,6 +12,7 @@ import '../in_app_webview/in_app_webview_controller.dart'; import '../in_app_webview/in_app_webview_settings.dart'; import '../util.dart'; +import '../print_job/main.dart'; import 'in_app_browser_settings.dart'; class InAppBrowserAlreadyOpenedException implements Exception { @@ -724,14 +725,24 @@ class InAppBrowser { ///- iOS void onUpdateVisitedHistory(Uri? url, bool? isReload) {} + ///Use [onPrintRequest] instead + @Deprecated("Use onPrintRequest instead") + void onPrint(Uri? url) {} + ///Event fired when `window.print()` is called from JavaScript side. + ///Return `true` if you want to handle the print job. + ///Otherwise return `false`, so the [PrintJobController] will be handled and disposed automatically by the system. /// ///[url] represents the url on which is called. /// + ///[printJobController] represents the controller of the print job created. + /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS - void onPrint(Uri? url) {} + Future? onPrintRequest(Uri? url, PrintJobController? printJobController) { + return null; + } ///Event fired when an HTML element of the webview has been clicked and held. /// diff --git a/lib/src/in_app_webview/headless_in_app_webview.dart b/lib/src/in_app_webview/headless_in_app_webview.dart index 02aaa295..e9f9860c 100644 --- a/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/lib/src/in_app_webview/headless_in_app_webview.dart @@ -7,6 +7,7 @@ import 'package:flutter_inappwebview/src/util.dart'; import '../context_menu.dart'; import '../types/main.dart'; +import '../print_job/main.dart'; import 'webview.dart'; import 'in_app_webview_controller.dart'; import 'in_app_webview_settings.dart'; @@ -74,7 +75,8 @@ class HeadlessInAppWebView implements WebView { this.onScrollChanged, @Deprecated('Use onDownloadStartRequest instead') this.onDownloadStart, this.onDownloadStartRequest, - @Deprecated('Use onLoadResourceWithCustomScheme instead') this.onLoadResourceCustomScheme, + @Deprecated('Use onLoadResourceWithCustomScheme instead') + this.onLoadResourceCustomScheme, this.onLoadResourceWithCustomScheme, this.onCreateWindow, this.onCloseWindow, @@ -90,7 +92,8 @@ class HeadlessInAppWebView implements WebView { this.onAjaxProgress, this.shouldInterceptFetchRequest, this.onUpdateVisitedHistory, - this.onPrint, + @Deprecated("Use onPrintRequest instead") this.onPrint, + this.onPrintRequest, this.onLongPressHitTestResult, this.onEnterFullscreen, this.onExitFullscreen, @@ -460,7 +463,8 @@ class HeadlessInAppWebView implements WebView { @override Future Function( - InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme; + InAppWebViewController controller, WebResourceRequest request)? + onLoadResourceWithCustomScheme; @override void Function(InAppWebViewController controller, Uri? url)? onLoadStart; @@ -472,9 +476,15 @@ class HeadlessInAppWebView implements WebView { void Function(InAppWebViewController controller, InAppWebViewHitTestResult hitTestResult)? onLongPressHitTestResult; + ///Use [onPrintRequest] instead + @Deprecated("Use onPrintRequest instead") @override void Function(InAppWebViewController controller, Uri? url)? onPrint; + @override + Future Function(InAppWebViewController controller, Uri? url, + PrintJobController? printJobController)? onPrintRequest; + @override void Function(InAppWebViewController controller, int progress)? onProgressChanged; diff --git a/lib/src/in_app_webview/in_app_webview.dart b/lib/src/in_app_webview/in_app_webview.dart index 9410736c..971f8bbe 100755 --- a/lib/src/in_app_webview/in_app_webview.dart +++ b/lib/src/in_app_webview/in_app_webview.dart @@ -13,6 +13,7 @@ import '../web/web_platform_manager.dart'; import '../context_menu.dart'; import '../types/main.dart'; +import '../print_job/main.dart'; import 'webview.dart'; import 'in_app_webview_controller.dart'; @@ -65,7 +66,8 @@ class InAppWebView extends StatefulWidget implements WebView { this.onScrollChanged, @Deprecated('Use onDownloadStartRequest instead') this.onDownloadStart, this.onDownloadStartRequest, - @Deprecated('Use onLoadResourceWithCustomScheme instead') this.onLoadResourceCustomScheme, + @Deprecated('Use onLoadResourceWithCustomScheme instead') + this.onLoadResourceCustomScheme, this.onLoadResourceWithCustomScheme, this.onCreateWindow, this.onCloseWindow, @@ -81,7 +83,8 @@ class InAppWebView extends StatefulWidget implements WebView { this.onAjaxProgress, this.shouldInterceptFetchRequest, this.onUpdateVisitedHistory, - this.onPrint, + @Deprecated("Use onPrintRequest instead") this.onPrint, + this.onPrintRequest, this.onLongPressHitTestResult, this.onEnterFullscreen, this.onExitFullscreen, @@ -345,7 +348,8 @@ class InAppWebView extends StatefulWidget implements WebView { @override final Future Function( - InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme; + InAppWebViewController controller, WebResourceRequest request)? + onLoadResourceWithCustomScheme; @override final void Function(InAppWebViewController controller, Uri? url)? onLoadStart; @@ -357,9 +361,15 @@ class InAppWebView extends StatefulWidget implements WebView { final void Function(InAppWebViewController controller, InAppWebViewHitTestResult hitTestResult)? onLongPressHitTestResult; + ///Use [onPrintRequest] instead + @Deprecated("Use onPrintRequest instead") @override final void Function(InAppWebViewController controller, Uri? url)? onPrint; + @override + final Future Function(InAppWebViewController controller, Uri? url, + PrintJobController? printJobController)? onPrintRequest; + @override final void Function(InAppWebViewController controller, int progress)? onProgressChanged; diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index beb29205..385364f8 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -28,6 +28,8 @@ import 'in_app_webview_settings.dart'; import 'webview.dart'; import '_static_channel.dart'; +import '../print_job/main.dart'; + ///List of forbidden names for JavaScript handlers. // ignore: non_constant_identifier_names final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ @@ -36,7 +38,7 @@ final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ "onAjaxReadyStateChange", "onAjaxProgress", "shouldInterceptFetchRequest", - "onPrint", + "onPrintRequest", "onWindowFocus", "onWindowBlur", "callAsyncJavaScript", @@ -1089,15 +1091,31 @@ class InAppWebViewController { _webview!.onWindowBlur!(this); else if (_inAppBrowser != null) _inAppBrowser!.onWindowBlur(); break; - case "onPrint": - if ((_webview != null && _webview!.onPrint != null) || + case "onPrintRequest": + if ((_webview != null && + (_webview!.onPrintRequest != null || + // ignore: deprecated_member_use_from_same_package + _webview!.onPrint != null)) || _inAppBrowser != null) { String? url = call.arguments["url"]; + String? printJobId = call.arguments["printJobId"]; Uri? uri = url != null ? Uri.parse(url) : null; - if (_webview != null && _webview!.onPrint != null) - _webview!.onPrint!(this, uri); - else + PrintJobController? printJob = + printJobId != null ? PrintJobController(id: printJobId) : null; + + if (_webview != null) { + if (_webview!.onPrintRequest != null) + return await _webview!.onPrintRequest!(this, uri, printJob); + else { + // ignore: deprecated_member_use_from_same_package + _webview!.onPrint!(this, uri); + return false; + } + } else { + // ignore: deprecated_member_use_from_same_package _inAppBrowser!.onPrint(uri); + return await _inAppBrowser!.onPrintRequest(uri, printJob); + } } break; case "onInjectedScriptLoaded": @@ -1235,17 +1253,6 @@ class InAppWebViewController { await _inAppBrowser!.shouldInterceptFetchRequest(request)); } return null; - case "onPrint": - if ((_webview != null && _webview!.onPrint != null) || - _inAppBrowser != null) { - String? url = args[0]; - Uri? uri = url != null ? Uri.parse(url) : null; - if (_webview != null && _webview!.onPrint != null) - _webview!.onPrint!(this, uri); - else - _inAppBrowser!.onPrint(uri); - } - return null; case "onWindowFocus": if (_webview != null && _webview!.onWindowFocus != null) _webview!.onWindowFocus!(this); @@ -2273,17 +2280,25 @@ class InAppWebViewController { ///Prints the current page. /// - ///**NOTE**: available on Android 21+. + ///To obtain the [PrintJobController], use [settings] argument with [PrintJobSettings.handledByClient] to `true`. + ///Otherwise this method will return `null` and the [PrintJobController] will be handled and disposed automatically by the system. /// - ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. + ///**NOTE**: available on Android 19+. + /// + ///**NOTE for Web**: this method will have effect only if the iframe has the same origin. Also, [PrintJobController] is always `null`. /// ///**Supported Platforms/Implementations**: - ///- Android native WebView ([Official API - PrintManager](https://developer.android.com/reference/android/print/PrintManager)) - ///- iOS ([Official API - UIPrintInteractionController](https://developer.apple.com/documentation/uikit/uiprintinteractioncontroller)) + ///- Android native WebView ([Official API - PrintManager.print](https://developer.android.com/reference/android/print/PrintManager#print(java.lang.String,%20android.print.PrintDocumentAdapter,%20android.print.PrintAttributes))) + ///- iOS ([Official API - UIPrintInteractionController.present](https://developer.apple.com/documentation/uikit/uiprintinteractioncontroller/1618149-present)) ///- Web ([Official API - Window.print](https://developer.mozilla.org/en-US/docs/Web/API/Window/print)) - Future printCurrentPage() async { + Future printCurrentPage({PrintJobSettings? settings}) async { Map args = {}; - await _channel.invokeMethod('printCurrentPage', args); + args.putIfAbsent("settings", () => settings?.toMap()); + String? jobId = await _channel.invokeMethod('printCurrentPage', args); + if (jobId != null) { + return PrintJobController(id: jobId); + } + return null; } ///Gets the height of the HTML content. @@ -3205,7 +3220,7 @@ class InAppWebViewController { /// ///[pdfConfiguration] represents the object that specifies the portion of the web view to capture as PDF data. /// - ///**NOTE**: available only on iOS 14.0+. + ///**NOTE for iOS**: available only on iOS 14.0+. /// ///**Supported Platforms/Implementations**: ///- iOS ([Official API - WKWebView.createPdf](https://developer.apple.com/documentation/webkit/wkwebview/3650490-createpdf)) @@ -3223,7 +3238,7 @@ class InAppWebViewController { ///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+. + ///**NOTE for iOS**: available only on iOS 14.0+. /// ///**Supported Platforms/Implementations**: ///- iOS ([Official API - WKWebView.createWebArchiveData](https://developer.apple.com/documentation/webkit/wkwebview/3650491-createwebarchivedata)) @@ -3472,7 +3487,7 @@ class InAppWebViewController { /// ///[urlScheme] represents the URL scheme associated with the resource. /// - ///**NOTE**: available only on iOS 11.0+. + ///**NOTE for iOS**: available only on iOS 11.0+. /// ///**Supported Platforms/Implementations**: ///- iOS ([Official API - WKWebView.handlesURLScheme](https://developer.apple.com/documentation/webkit/wkwebview/2875370-handlesurlscheme)) diff --git a/lib/src/in_app_webview/main.dart b/lib/src/in_app_webview/main.dart index 105ca7ef..7eac74ab 100644 --- a/lib/src/in_app_webview/main.dart +++ b/lib/src/in_app_webview/main.dart @@ -4,4 +4,4 @@ export 'in_app_webview_controller.dart'; export 'in_app_webview_settings.dart'; export 'headless_in_app_webview.dart'; export 'android/main.dart'; -export 'apple/main.dart'; +export 'apple/main.dart'; \ No newline at end of file diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index cc4aefcb..2fcd0fb0 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -9,15 +9,20 @@ import '../types/main.dart'; import 'in_app_webview_controller.dart'; import 'in_app_webview_settings.dart'; import 'headless_in_app_webview.dart'; +import '../print_job/main.dart'; import '../debug_logging_settings.dart'; ///Abstract class that represents a WebView. Used by [InAppWebView], [HeadlessInAppWebView] and the WebView of [InAppBrowser]. abstract class WebView { ///Debug settings used by [InAppWebView], [HeadlessInAppWebView] and [InAppBrowser]. - ///The default value excludes the [WebView.onScrollChanged] and [WebView.onOverScrolled] events. + ///The default value excludes the [WebView.onScrollChanged], [WebView.onOverScrolled] and [WebView.onReceivedIcon] events. static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings( - excludeFilter: [RegExp(r"onScrollChanged"), RegExp(r"onOverScrolled")]); + excludeFilter: [ + RegExp(r"onScrollChanged"), + RegExp(r"onOverScrolled"), + RegExp(r"onReceivedIcon") + ]); ///The window id of a [CreateWindowAction.windowId]. final int? windowId; @@ -184,7 +189,8 @@ abstract class WebView { ///- Android native WebView ///- iOS ([Official API - WKURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkurlschemehandler)) final Future Function( - InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme; + InAppWebViewController controller, WebResourceRequest request)? + onLoadResourceWithCustomScheme; ///Event fired when the [WebView] requests the host application to create a new window, ///for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side. @@ -433,17 +439,27 @@ abstract class WebView { InAppWebViewController controller, Uri? url, bool? isReload)? onUpdateVisitedHistory; + ///Use [onPrintRequest] instead + @Deprecated("Use onPrintRequest instead") + final void Function(InAppWebViewController controller, Uri? url)? onPrint; + ///Event fired when `window.print()` is called from JavaScript side. + ///Return `true` if you want to handle the print job. + ///Otherwise return `false`, so the [PrintJobController] will be handled and disposed automatically by the system. /// ///[url] represents the url on which is called. /// + ///[printJobController] represents the controller of the print job created. + ///**NOTE**: on Web, it is always `null` + /// ///**NOTE for Web**: this event will be called only if the iframe has the same origin. /// ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS ///- Web - final void Function(InAppWebViewController controller, Uri? url)? onPrint; + final Future Function(InAppWebViewController controller, Uri? url, + PrintJobController? printJobController)? onPrintRequest; ///Event fired when an HTML element of the webview has been clicked and held. /// @@ -967,7 +983,8 @@ abstract class WebView { @Deprecated('Use onDownloadStartRequest instead') this.onDownloadStart, this.onDownloadStartRequest, - @Deprecated('Use onLoadResourceWithCustomScheme instead') this.onLoadResourceCustomScheme, + @Deprecated('Use onLoadResourceWithCustomScheme instead') + this.onLoadResourceCustomScheme, this.onLoadResourceWithCustomScheme, this.onCreateWindow, this.onCloseWindow, @@ -983,7 +1000,9 @@ abstract class WebView { this.onAjaxProgress, this.shouldInterceptFetchRequest, this.onUpdateVisitedHistory, - this.onPrint, + @Deprecated("Use onPrintRequest instead") + this.onPrint, + this.onPrintRequest, this.onLongPressHitTestResult, this.onEnterFullscreen, this.onExitFullscreen, diff --git a/lib/src/main.dart b/lib/src/main.dart index 9f53aab5..44ad161c 100644 --- a/lib/src/main.dart +++ b/lib/src/main.dart @@ -15,4 +15,5 @@ export 'context_menu.dart'; export 'pull_to_refresh/main.dart'; export 'web_message/main.dart'; export 'web_authentication_session/main.dart'; +export 'print_job/main.dart'; export 'debug_logging_settings.dart'; diff --git a/lib/src/print_job/main.dart b/lib/src/print_job/main.dart new file mode 100644 index 00000000..ebf92a43 --- /dev/null +++ b/lib/src/print_job/main.dart @@ -0,0 +1,2 @@ +export 'print_job_controller.dart'; +export 'print_job_settings.dart'; \ No newline at end of file diff --git a/lib/src/print_job/print_job_controller.dart b/lib/src/print_job/print_job_controller.dart new file mode 100644 index 00000000..d9f9cb7c --- /dev/null +++ b/lib/src/print_job/print_job_controller.dart @@ -0,0 +1,103 @@ +import 'package:flutter/services.dart'; +import '../types/print_job_info.dart'; +import '../in_app_webview/in_app_webview_controller.dart'; + +///A completion handler for the [PrintJobController]. +typedef PrintJobCompletionHandler = Future Function(bool completed, String? error)?; + +///Class representing a print job eventually returned by [InAppWebViewController.printCurrentPage]. +class PrintJobController { + ///Print job ID. + final String id; + + late MethodChannel _channel; + + ///A completion handler used to handle the conclusion of the print job (for instance, to reset state) and to handle any errors encountered in printing. + /// + ///**Supported Platforms/Implementations**: + ///- iOS ([Official API - UIPrintInteractionController.CompletionHandler](https://developer.apple.com/documentation/uikit/uiprintinteractioncontroller/completionhandler)) + PrintJobCompletionHandler onComplete; + + PrintJobController( + {required this.id}) { + this._channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_$id'); + this._channel.setMethodCallHandler(_handleMethod); + } + + Future _handleMethod(MethodCall call) async { + switch (call.method) { + case "onComplete": + bool completed = call.arguments["completed"]; + String? error = call.arguments["error"]; + if (onComplete != null) { + onComplete!(completed, error); + } + break; + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + } + + ///Cancels this print job. + ///You can request cancellation of a queued, started, blocked, or failed print job. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJob.cancel](https://developer.android.com/reference/android/print/PrintJob#cancel())) + Future cancel() async { + Map args = {}; + await _channel.invokeMethod('cancel', args); + } + + ///Restarts this print job. + ///You can request restart of a failed print job. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJob.restart](https://developer.android.com/reference/android/print/PrintJob#restart())) + Future restart() async { + Map args = {}; + await _channel.invokeMethod('restart', args); + } + + ///Dismisses the printing-options sheet or popover. + /// + ///You should dismiss the printing options when they are presented in a sheet or + ///animated from a rectangle and the user changes the orientation of the device. + ///(This, of course, assumes your application responds to orientation changes.) + ///You should then present the printing options again once the new orientation takes effect. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + Future dismiss({bool animated: true}) async { + Map args = {}; + args.putIfAbsent("animated", () => animated); + await _channel.invokeMethod('dismiss', args); + } + + ///Gets the [PrintJobInfo] that describes this job. + /// + ///**NOTE**: The returned info object is a snapshot of the + ///current print job state. Every call to this method returns a fresh + ///info object that reflects the current print job state. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJob.getInfo](https://developer.android.com/reference/android/print/PrintJob#getInfo())) + ///- iOS + Future getInfo() async { + Map args = {}; + Map? infoMap = + (await _channel.invokeMethod('getInfo', args)) + ?.cast(); + return PrintJobInfo.fromMap(infoMap); + } + + ///Disposes the print job. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + Future dispose() async { + Map args = {}; + await _channel.invokeMethod('dispose', args); + } +} \ No newline at end of file diff --git a/lib/src/print_job/print_job_settings.dart b/lib/src/print_job/print_job_settings.dart new file mode 100644 index 00000000..d55b2331 --- /dev/null +++ b/lib/src/print_job/print_job_settings.dart @@ -0,0 +1,250 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_inappwebview/src/types/print_job_media_size.dart'; + +import '../types/main.dart'; +import '../util.dart'; +import 'print_job_controller.dart'; + +///Class that represents the settings of a [PrintJobController]. +class PrintJobSettings { + ///Set this to `true` to handle the [PrintJobController]. + ///Otherwise, it will be handled and disposed automatically by the system. + ///The default value is `false`. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + bool handledByClient; + + ///The name of the print job. + ///An application should set this property to a name appropriate to the content being printed. + ///The default job name is the current webpage title concatenated with the "Document" word at the end. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + String? jobName; + + ///`true` to animate the display of the sheet, `false` to display the sheet immediately. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + bool animated; + + ///The orientation of the printed content, portrait or landscape. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + PrintJobOrientation? orientation; + + ///The number of pages to render. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + int? numberOfPages; + + ///Force rendering quality. + /// + ///**NOTE for iOS**: available only on iOS 14.5+. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + PrintJobRenderingQuality? forceRenderingQuality; + + ///The margins for each printed page. + ///Margins define the white space around the content where the left margin defines + ///the amount of white space on the left of the content and so on. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + EdgeInsets? margins; + + ///The media size. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + PrintJobMediaSize? mediaSize; + + ///The color mode. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + PrintJobColorMode? colorMode; + + ///The duplex mode to use for the print job. + /// + ///**NOTE for Android native WebView**: available only on Android 23+. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + PrintJobDuplexMode? duplexMode; + + ///The kind of printable content. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + PrintJobOutputType? outputType; + + ///The supported resolution in DPI (dots per inch). + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + PrintJobResolution? resolution; + + ///A Boolean value that determines whether the printing options include the number of copies. + ///The default value is `true`. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + bool showsNumberOfCopies; + + ///A Boolean value that determines whether the paper selection menu displays. + ///The default value of this property is `false`. + ///Setting the value to `true` enables a paper selection menu on printers that support different types of paper and have more than one paper type loaded. + ///On printers where only one paper type is available, no paper selection menu is displayed, regardless of the value of this property. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + bool showsPaperSelectionForLoadedPapers; + + ///A Boolean value that determines whether the printing options include the paper orientation control when available. + ///The default value is `true`. + /// + ///**NOTE for iOS**: available only on iOS 15.0+. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + bool showsPaperOrientation; + + ///The height of the page footer. + /// + ///The footer is measured in points from the bottom of [printableRect] and is below the content area. + ///The default footer height is `0.0`. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + double? footerHeight; + + ///The height of the page header. + /// + ///The header is measured in points from the top of [printableRect] and is above the content area. + ///The default header height is `0.0`. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + double? headerHeight; + + ///The maximum height of the content area. + /// + ///The Print Formatter uses this value to determine where the content rectangle begins on the first page. + ///It compares the value of this property with the printing rectangle’s height minus the header and footer heights and + ///the top inset value; it uses the lower of the two values. + ///The default value of this property is the maximum float value. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + double? maximumContentHeight; + + ///The maximum width of the content area. + /// + ///The Print Formatter uses this value to determine the maximum width of the content rectangle. + ///It compares the value of this property with the printing rectangle’s width minus the left and right inset values and uses the lower of the two. + ///The default value of this property is the maximum float value. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + double? maximumContentWidth; + + PrintJobSettings( + {this.handledByClient = false, + this.jobName, + this.animated = true, + this.orientation, + this.numberOfPages, + this.forceRenderingQuality, + this.margins, + this.mediaSize, + this.colorMode, + this.duplexMode, + this.outputType, + this.resolution, + this.showsNumberOfCopies = true, + this.showsPaperSelectionForLoadedPapers = false, + this.showsPaperOrientation = true, + this.maximumContentHeight, + this.maximumContentWidth, + this.footerHeight, + this.headerHeight}); + + ///Gets a possible [PrintJobSettings] instance from a [Map] value. + static PrintJobSettings fromMap(Map map) { + var settings = PrintJobSettings(); + settings.handledByClient = map["handledByClient"]; + settings.jobName = map["jobName"]; + settings.animated = map["animated"]; + settings.orientation = PrintJobOrientation.fromValue(map["orientation"]); + settings.numberOfPages = map["numberOfPages"]; + settings.forceRenderingQuality = + PrintJobRenderingQuality.fromValue(map["forceRenderingQuality"]); + settings.margins = + MapEdgeInsets.fromMap(map["margins"]?.cast()); + settings.mediaSize = + PrintJobMediaSize.fromMap(map["mediaSize"]?.cast()); + settings.colorMode = PrintJobColorMode.fromValue(map["colorMode"]); + settings.duplexMode = PrintJobDuplexMode.fromNativeValue(map["duplexMode"]); + settings.outputType = PrintJobOutputType.fromValue(map["outputType"]); + settings.resolution = + PrintJobResolution.fromMap(map["resolution"]?.cast()); + settings.showsNumberOfCopies = map["showsNumberOfCopies"]; + settings.showsPaperSelectionForLoadedPapers = + map["showsPaperSelectionForLoadedPapers"]; + settings.showsPaperOrientation = map["showsPaperOrientation"]; + settings.maximumContentHeight = map["maximumContentHeight"]; + settings.maximumContentWidth = map["maximumContentWidth"]; + settings.footerHeight = map["footerHeight"]; + settings.headerHeight = map["headerHeight"]; + return settings; + } + + ///Converts instance to a map. + Map toMap() { + return { + "handledByClient": handledByClient, + "jobName": jobName, + "animated": animated, + "orientation": orientation?.toValue(), + "numberOfPages": numberOfPages, + "forceRenderingQuality": forceRenderingQuality?.toValue(), + "margins": margins?.toMap(), + "mediaSize": mediaSize?.toMap(), + "colorMode": colorMode?.toValue(), + "duplexMode": duplexMode?.toNativeValue(), + "outputType": outputType?.toValue(), + "resolution": resolution?.toMap(), + "showsNumberOfCopies": showsNumberOfCopies, + "showsPaperSelectionForLoadedPapers": showsPaperSelectionForLoadedPapers, + "showsPaperOrientation": showsPaperOrientation, + "maximumContentHeight": maximumContentHeight, + "maximumContentWidth": maximumContentWidth, + "footerHeight": footerHeight, + "headerHeight": headerHeight, + }; + } + + ///Gets a copy of the current instance. + PrintJobSettings copy() { + return PrintJobSettings.fromMap(this.toMap()); + } + + ///Converts instance to a map. + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/src/types/in_app_webview_rect.dart b/lib/src/types/in_app_webview_rect.dart index 8c3a85ac..022beed2 100644 --- a/lib/src/types/in_app_webview_rect.dart +++ b/lib/src/types/in_app_webview_rect.dart @@ -20,6 +20,20 @@ class InAppWebViewRect { assert(this.x >= 0 && this.y >= 0 && this.width >= 0 && this.height >= 0); } + ///Gets a possible [InAppWebViewRect] instance from a [Map] value. + static InAppWebViewRect? fromMap(Map? map) { + if (map == null) { + return null; + } + + return InAppWebViewRect( + x: map["x"], + y: map["y"], + width: map["width"], + height: map["height"], + ); + } + ///Converts instance to a map. Map toMap() { return {"x": x, "y": y, "width": width, "height": height}; diff --git a/lib/src/types/main.dart b/lib/src/types/main.dart index 73ad6b2e..f984c4f5 100644 --- a/lib/src/types/main.dart +++ b/lib/src/types/main.dart @@ -149,4 +149,14 @@ export 'proxy_rule.dart'; export 'proxy_scheme_filter.dart'; export 'force_dark_strategy.dart'; export 'url_request_attribution.dart'; -export 'web_authentication_session_error.dart'; \ No newline at end of file +export 'web_authentication_session_error.dart'; +export 'print_job_info.dart'; +export 'print_job_state.dart'; +export 'print_job_orientation.dart'; +export 'print_job_rendering_quality.dart'; +export 'print_job_media_size.dart'; +export 'print_job_color_mode.dart'; +export 'print_job_duplex_mode.dart'; +export 'print_job_output_type.dart'; +export 'print_job_resolution.dart'; +export 'print_job_attributes.dart'; \ No newline at end of file diff --git a/lib/src/types/permission_resource_type.dart b/lib/src/types/permission_resource_type.dart index b773a3fd..0e9e4c90 100644 --- a/lib/src/types/permission_resource_type.dart +++ b/lib/src/types/permission_resource_type.dart @@ -30,7 +30,7 @@ class PermissionResourceType { return null; } - ///Gets a possible [PermissionResourceType] instance from a value. + ///Gets a possible [PermissionResourceType] instance from a native value. static PermissionResourceType? fromNativeValue(dynamic value) { if (value != null) { try { diff --git a/lib/src/types/print_job_attributes.dart b/lib/src/types/print_job_attributes.dart new file mode 100644 index 00000000..127c14bf --- /dev/null +++ b/lib/src/types/print_job_attributes.dart @@ -0,0 +1,173 @@ +import 'package:flutter/rendering.dart'; + +import '../util.dart'; +import '../print_job/main.dart'; +import '../types/main.dart'; + +///Class representing the attributes of a [PrintJobController]. +///These attributes describe how the printed content should be laid out. +class PrintJobAttributes { + ///The color mode. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + int? colorMode; + + ///The duplex mode to use for the print job. + /// + ///**NOTE for Android native WebView**: available only on Android 23+. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + PrintJobDuplexMode? duplex; + + ///The orientation of the printed content, portrait or landscape. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + PrintJobOrientation? orientation; + + ///The media size. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + PrintJobMediaSize? mediaSize; + + ///The supported resolution in DPI (dots per inch). + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + PrintJobResolution? resolution; + + ///The margins for each printed page. + ///Margins define the white space around the content where the left margin defines + ///the amount of white space on the left of the content and so on. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + EdgeInsets? margins; + + ///The height of the page footer. + /// + ///The footer is measured in points from the bottom of [printableRect] and is below the content area. + ///The default footer height is `0.0`. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + double? footerHeight; + + ///The height of the page header. + /// + ///The header is measured in points from the top of [printableRect] and is above the content area. + ///The default header height is `0.0`. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + double? headerHeight; + + ///The area in which printing can occur. + /// + ///The value of this property is a rectangle that defines the area in which the printer can print content. + ///Sometimes this is referred to as the imageable area of the paper. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + InAppWebViewRect? printableRect; + + ///The size of the paper used for printing. + /// + ///The value of this property is a rectangle that defines the size of paper chosen for the print job. + ///The origin is always (0,0). + /// + ///**Supported Platforms/Implementations**: + ///- iOS + InAppWebViewRect? paperRect; + + ///The maximum height of the content area. + /// + ///The Print Formatter uses this value to determine where the content rectangle begins on the first page. + ///It compares the value of this property with the printing rectangle’s height minus the header and footer heights and + ///the top inset value; it uses the lower of the two values. + ///The default value of this property is the maximum float value. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + double? maximumContentHeight; + + ///The maximum width of the content area. + /// + ///The Print Formatter uses this value to determine the maximum width of the content rectangle. + ///It compares the value of this property with the printing rectangle’s width minus the left and right inset values and uses the lower of the two. + ///The default value of this property is the maximum float value. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + double? maximumContentWidth; + + PrintJobAttributes( + {this.colorMode, + this.duplex, + this.orientation, + this.mediaSize, + this.resolution, + this.margins, + this.maximumContentHeight, + this.maximumContentWidth, + this.footerHeight, + this.headerHeight, + this.paperRect, + this.printableRect}); + + ///Gets a possible [PrintJobAttributes] instance from a [Map] value. + static PrintJobAttributes? fromMap(Map? map) { + if (map == null) { + return null; + } + + return PrintJobAttributes( + colorMode: map["colorMode"], + duplex: PrintJobDuplexMode.fromNativeValue(map["duplex"]), + orientation: PrintJobOrientation.fromValue(map["orientation"]), + mediaSize: PrintJobMediaSize.fromMap( + map["mediaSize"]?.cast()), + resolution: PrintJobResolution.fromMap( + map["resolution"]?.cast()), + margins: MapEdgeInsets.fromMap(map["margins"]?.cast()), + maximumContentHeight: map["maximumContentHeight"], + maximumContentWidth: map["maximumContentWidth"], + footerHeight: map["footerHeight"], + headerHeight: map["headerHeight"], + paperRect: InAppWebViewRect.fromMap(map["paperRect"]?.cast()), + printableRect: InAppWebViewRect.fromMap(map["printableRect"]?.cast())); + } + + ///Converts instance to a map. + Map toMap() { + return { + "colorMode": colorMode, + "duplex": duplex?.toNativeValue(), + "orientation": orientation?.toValue(), + "mediaSize": mediaSize?.toMap(), + "resolution": resolution?.toMap(), + "margins": margins?.toMap(), + "maximumContentHeight": maximumContentHeight, + "maximumContentWidth": maximumContentWidth, + "footerHeight": footerHeight, + "headerHeight": headerHeight, + "paperRect": paperRect?.toMap(), + "printableRect": printableRect?.toMap(), + }; + } + + ///Converts instance to a map. + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/src/types/print_job_color_mode.dart b/lib/src/types/print_job_color_mode.dart new file mode 100644 index 00000000..5f7fb1f5 --- /dev/null +++ b/lib/src/types/print_job_color_mode.dart @@ -0,0 +1,52 @@ +import '../print_job/main.dart'; + +///Class representing how the printed content of a [PrintJobController] should be laid out. +class PrintJobColorMode { + final int _value; + + const PrintJobColorMode._internal(this._value); + + ///Set of all values of [PrintJobColorMode]. + static final Set values = [ + PrintJobColorMode.MONOCHROME, + PrintJobColorMode.COLOR, + ].toSet(); + + ///Gets a possible [PrintJobColorMode] instance from an [int] value. + static PrintJobColorMode? fromValue(int? value) { + if (value != null) { + try { + return PrintJobColorMode.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets [int] value. + int toValue() => _value; + + @override + String toString() { + switch (_value) { + case 1: + return "MONOCHROME"; + case 2: + default: + return "COLOR"; + } + } + + ///Monochrome color scheme, for example one color is used. + static const MONOCHROME = const PrintJobColorMode._internal(1); + + ///Color color scheme, for example many colors are used. + static const COLOR = const PrintJobColorMode._internal(2); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} diff --git a/lib/src/types/print_job_duplex_mode.dart b/lib/src/types/print_job_duplex_mode.dart new file mode 100644 index 00000000..d567e107 --- /dev/null +++ b/lib/src/types/print_job_duplex_mode.dart @@ -0,0 +1,90 @@ +import 'package:flutter/foundation.dart'; + +import '../print_job/main.dart'; + +///Class representing the orientation of a [PrintJobController]. +class PrintJobDuplexMode { + final String _value; + final int _nativeValue; + + const PrintJobDuplexMode._internal(this._value, this._nativeValue); + + ///Set of all values of [PrintJobDuplexMode]. + static final Set values = [ + PrintJobDuplexMode.NONE, + PrintJobDuplexMode.LONG_EDGE, + PrintJobDuplexMode.SHORT_EDGE, + ].toSet(); + + ///Gets a possible [PrintJobDuplexMode] instance from a [String] value. + static PrintJobDuplexMode? fromValue(String? value) { + if (value != null) { + try { + return PrintJobDuplexMode.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets a possible [PrintJobDuplexMode] instance from an [int] native value. + static PrintJobDuplexMode? fromNativeValue(int? value) { + if (value != null) { + try { + return PrintJobDuplexMode.values + .firstWhere((element) => element.toNativeValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets [String] value. + String toValue() => _value; + + ///Gets native value. + int toNativeValue() => _nativeValue; + + @override + String toString() => _value; + + ///No double-sided (duplex) printing; single-sided printing only. + static final NONE = PrintJobDuplexMode._internal( + 'NONE', + (defaultTargetPlatform == TargetPlatform.android) + ? 1 + : ((defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS) + ? 0 + : 0)); + + ///Duplex printing that flips the back page along the long edge of the paper. + ///Pages are turned sideways along the long edge - like a book. + static final LONG_EDGE = PrintJobDuplexMode._internal( + 'LONG_EDGE', + (defaultTargetPlatform == TargetPlatform.android) + ? 2 + : ((defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS) + ? 1 + : 0)); + + ///Duplex print that flips the back page along the short edge of the paper. + ///Pages are turned upwards along the short edge - like a notepad. + static final SHORT_EDGE = PrintJobDuplexMode._internal( + 'SHORT_EDGE', + (defaultTargetPlatform == TargetPlatform.android) + ? 4 + : ((defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS) + ? 2 + : 0)); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} diff --git a/lib/src/types/print_job_info.dart b/lib/src/types/print_job_info.dart new file mode 100644 index 00000000..edf1d68f --- /dev/null +++ b/lib/src/types/print_job_info.dart @@ -0,0 +1,104 @@ +import 'print_job_state.dart'; +import '../print_job/main.dart'; +import '../types/main.dart'; + +///Class representing the description of a [PrintJobController]. +///Note that the print jobs state may change over time and this class represents a snapshot of this state. +class PrintJobInfo { + ///The state of the print job. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + PrintJobState? state; + + ///How many copies to print. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + int? copies; + + ///The number of pages to print. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + int? numberOfPages; + + ///The timestamp when the print job was created. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + int? creationTime; + + ///The human readable print job label. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + String? label; + + ///The unique id of the printer. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + String? printerId; + + ///The attributes of a print job. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + PrintJobAttributes? attributes; + + PrintJobInfo( + {this.state, + this.copies, + this.numberOfPages, + this.creationTime, + this.label, + this.printerId, + this.attributes}); + + ///Gets a possible [PrintJobInfo] instance from a [Map] value. + static PrintJobInfo? fromMap(Map? map) { + if (map == null) { + return null; + } + + return PrintJobInfo( + state: PrintJobState.fromValue(map["state"]), + copies: map["copies"], + numberOfPages: map["numberOfPages"], + creationTime: map["creationTime"], + label: map["label"], + printerId: map["printerId"], + attributes: PrintJobAttributes.fromMap( + map["attributes"]?.cast())); + } + + ///Converts instance to a map. + Map toMap() { + return { + "state": state?.toValue(), + "copies": copies, + "numberOfPages": numberOfPages, + "creationTime": creationTime, + "label": label, + "printerId": printerId, + "attributes": attributes?.toMap() + }; + } + + ///Converts instance to a map. + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/src/types/print_job_media_size.dart b/lib/src/types/print_job_media_size.dart new file mode 100644 index 00000000..1a3a7c93 --- /dev/null +++ b/lib/src/types/print_job_media_size.dart @@ -0,0 +1,455 @@ +import '../print_job/main.dart'; + +///Class representing the supported media size for a [PrintJobController]. +///Media size is the dimension of the media on which the content is printed. +class PrintJobMediaSize { + ///The unique media size id. + /// + ///It is unique amongst other media sizes supported by the printer. + ///This id is defined by the client that generated the media size + ///instance and should not be interpreted by other parties. + final String id; + + ///The human readable label. + final String? label; + + ///The media width in mils (thousandths of an inch). + final int widthMils; + + ///The media height in mils (thousandths of an inch). + final int heightMils; + + const PrintJobMediaSize( + {required this.id, + required this.widthMils, + required this.heightMils, + this.label}); + + ///Gets a possible [PrintJobMediaSize] instance from a [Map] value. + static PrintJobMediaSize? fromMap(Map? map) { + if (map == null) { + return null; + } + + return PrintJobMediaSize( + id: map["id"], + widthMils: map["widthMils"], + heightMils: map["heightMils"], + label: map["label"], + ); + } + + ///Converts instance to a map. + Map toMap() { + return { + "id": id, + "widthMils": widthMils, + "heightMils": heightMils, + "label": label + }; + } + + ///Converts instance to a map. + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } + + ///Unknown media size in portrait mode. + /// + ///**NOTE**: This is for specifying orientation without media size. + ///You should not use the dimensions reported by this instance. + static const UNKNOWN_PORTRAIT = const PrintJobMediaSize( + id: "UNKNOWN_PORTRAIT", widthMils: 1, heightMils: 0x7fffffff); + + ///Unknown media size in portrait mode. + /// + ///**NOTE**: This is for specifying orientation without media size. + ///You should not use the dimensions reported by this instance. + static const UNKNOWN_LANDSCAPE = const PrintJobMediaSize( + id: "UNKNOWN_LANDSCAPE", widthMils: 0x7fffffff, heightMils: 1); + + // ISO sizes + + ///ISO A0 media size: 841mm x 1189mm (33.11" x 46.81") + static const ISO_A0 = const PrintJobMediaSize( + id: "ISO_A0", widthMils: 33110, heightMils: 46810); + + ///ISO A1 media size: 594mm x 841mm (23.39" x 33.11") + static const ISO_A1 = const PrintJobMediaSize( + id: "ISO_A1", widthMils: 23390, heightMils: 33110); + + ///ISO A2 media size: 420mm x 594mm (16.54" x 23.39") + static const ISO_A2 = const PrintJobMediaSize( + id: "ISO_A2", widthMils: 16540, heightMils: 23390); + + ///ISO A3 media size: 297mm x 420mm (11.69" x 16.54") + static const ISO_A3 = const PrintJobMediaSize( + id: "ISO_A3", widthMils: 11690, heightMils: 16540); + + ///ISO A4 media size: 210mm x 297mm (8.27" x 11.69") + static const ISO_A4 = + const PrintJobMediaSize(id: "ISO_A4", widthMils: 8270, heightMils: 11690); + + ///ISO A5 media size: 148mm x 210mm (5.83" x 8.27") + static const ISO_A5 = + const PrintJobMediaSize(id: "ISO_A5", widthMils: 5830, heightMils: 8270); + + ///ISO A6 media size: 105mm x 148mm (4.13" x 5.83") + static const ISO_A6 = + const PrintJobMediaSize(id: "ISO_A6", widthMils: 4130, heightMils: 5830); + + ///ISO A7 media size: 74mm x 105mm (2.91" x 4.13") + static const ISO_A7 = + const PrintJobMediaSize(id: "ISO_A7", widthMils: 2910, heightMils: 4130); + + ///ISO A8 media size: 52mm x 74mm (2.05" x 2.91") + static const ISO_A8 = + const PrintJobMediaSize(id: "ISO_A8", widthMils: 2050, heightMils: 2910); + + ///ISO A9 media size: 37mm x 52mm (1.46" x 2.05") + static const ISO_A9 = + const PrintJobMediaSize(id: "ISO_A9", widthMils: 1460, heightMils: 2050); + + ///ISO A10 media size: 26mm x 37mm (1.02" x 1.46") + static const ISO_A10 = + const PrintJobMediaSize(id: "ISO_A10", widthMils: 1020, heightMils: 1460); + + ///ISO B0 media size: 1000mm x 1414mm (39.37" x 55.67") + static const ISO_B0 = const PrintJobMediaSize( + id: "ISO_B0", widthMils: 39370, heightMils: 55670); + + ///ISO B1 media size: 707mm x 1000mm (27.83" x 39.37") + static const ISO_B1 = const PrintJobMediaSize( + id: "ISO_B1", widthMils: 27830, heightMils: 39370); + + ///ISO B2 media size: 500mm x 707mm (19.69" x 27.83") + static const ISO_B2 = const PrintJobMediaSize( + id: "ISO_B2", widthMils: 19690, heightMils: 27830); + + ///ISO B3 media size: 353mm x 500mm (13.90" x 19.69") + static const ISO_B3 = const PrintJobMediaSize( + id: "ISO_B3", widthMils: 13900, heightMils: 19690); + + ///ISO B4 media size: 250mm x 353mm (9.84" x 13.90") + static const ISO_B4 = + const PrintJobMediaSize(id: "ISO_B4", widthMils: 9840, heightMils: 13900); + + ///ISO B5 media size: 176mm x 250mm (6.93" x 9.84") + static const ISO_B5 = + const PrintJobMediaSize(id: "ISO_B5", widthMils: 6930, heightMils: 9840); + + ///ISO B6 media size: 125mm x 176mm (4.92" x 6.93") + static const ISO_B6 = + const PrintJobMediaSize(id: "ISO_B6", widthMils: 4920, heightMils: 6930); + + ///ISO B7 media size: 88mm x 125mm (3.46" x 4.92") + static const ISO_B7 = + const PrintJobMediaSize(id: "ISO_B7", widthMils: 3460, heightMils: 4920); + + ///ISO B8 media size: 62mm x 88mm (2.44" x 3.46") + static const ISO_B8 = + const PrintJobMediaSize(id: "ISO_B8", widthMils: 2440, heightMils: 3460); + + ///ISO B9 media size: 44mm x 62mm (1.73" x 2.44") + static const ISO_B9 = + const PrintJobMediaSize(id: "ISO_B9", widthMils: 1730, heightMils: 2440); + + ///ISO B10 media size: 31mm x 44mm (1.22" x 1.73") + static const ISO_B10 = + const PrintJobMediaSize(id: "ISO_B10", widthMils: 1220, heightMils: 1730); + + ///ISO C0 media size: 917mm x 1297mm (36.10" x 51.06") + static const ISO_C0 = const PrintJobMediaSize( + id: "ISO_C0", widthMils: 36100, heightMils: 51060); + + ///ISO C1 media size: 648mm x 917mm (25.51" x 36.10") + static const ISO_C1 = const PrintJobMediaSize( + id: "ISO_C1", widthMils: 25510, heightMils: 36100); + + ///ISO C2 media size: 458mm x 648mm (18.03" x 25.51") + static const ISO_C2 = const PrintJobMediaSize( + id: "ISO_C2", widthMils: 18030, heightMils: 25510); + + ///ISO C3 media size: 324mm x 458mm (12.76" x 18.03") + static const ISO_C3 = const PrintJobMediaSize( + id: "ISO_C3", widthMils: 12760, heightMils: 18030); + + ///ISO C4 media size: 229mm x 324mm (9.02" x 12.76") + static const ISO_C4 = + const PrintJobMediaSize(id: "ISO_C4", widthMils: 9020, heightMils: 12760); + + ///ISO C5 media size: 162mm x 229mm (6.38" x 9.02") + static const ISO_C5 = + const PrintJobMediaSize(id: "ISO_C5", widthMils: 6380, heightMils: 9020); + + ///ISO C6 media size: 114mm x 162mm (4.49" x 6.38") + static const ISO_C6 = + const PrintJobMediaSize(id: "ISO_C6", widthMils: 4490, heightMils: 6380); + + ///ISO C7 media size: 81mm x 114mm (3.19" x 4.49") + static const ISO_C7 = + const PrintJobMediaSize(id: "ISO_C7", widthMils: 3190, heightMils: 4490); + + ///ISO C8 media size: 57mm x 81mm (2.24" x 3.19") + static const ISO_C8 = + const PrintJobMediaSize(id: "ISO_C8", widthMils: 2240, heightMils: 3190); + + ///ISO C9 media size: 40mm x 57mm (1.57" x 2.24") + static const ISO_C9 = + const PrintJobMediaSize(id: "ISO_C9", widthMils: 1570, heightMils: 2240); + + ///ISO C10 media size: 28mm x 40mm (1.10" x 1.57") + static const ISO_C10 = + const PrintJobMediaSize(id: "ISO_C10", widthMils: 1100, heightMils: 1570); + + // North America + + ///North America Letter media size: 8.5" x 11" (279mm x 216mm) + static const NA_LETTER = const PrintJobMediaSize( + id: "NA_LETTER", widthMils: 8500, heightMils: 11000); + + ///North America Government-Letter media size: 8.0" x 10.5" (203mm x 267mm) + static const NA_GOVT_LETTER = const PrintJobMediaSize( + id: "NA_GOVT_LETTER", widthMils: 8000, heightMils: 10500); + + ///North America Legal media size: 8.5" x 14" (216mm x 356mm) + static const NA_LEGAL = const PrintJobMediaSize( + id: "NA_LEGAL", widthMils: 8500, heightMils: 14000); + + ///North America Junior Legal media size: 8.0" x 5.0" (203mm × 127mm) + static const NA_JUNIOR_LEGAL = const PrintJobMediaSize( + id: "NA_JUNIOR_LEGAL", widthMils: 8000, heightMils: 5000); + + ///North America Ledger media size: 17" x 11" (432mm × 279mm) + static const NA_LEDGER = const PrintJobMediaSize( + id: "NA_LEDGER", widthMils: 17000, heightMils: 11000); + + ///North America Tabloid media size: 11" x 17" (279mm × 432mm) + static const NA_TABLOID = const PrintJobMediaSize( + id: "NA_TABLOID", widthMils: 11000, heightMils: 17000); + + ///North America Index Card 3x5 media size: 3" x 5" (76mm x 127mm) + static const NA_INDEX_3X5 = const PrintJobMediaSize( + id: "NA_INDEX_3X5", widthMils: 3000, heightMils: 5000); + + ///North America Index Card 4x6 media size: 4" x 6" (102mm x 152mm) + static const NA_INDEX_4X6 = const PrintJobMediaSize( + id: "NA_INDEX_4X6", widthMils: 4000, heightMils: 6000); + + ///North America Index Card 5x8 media size: 5" x 8" (127mm x 203mm) + static const NA_INDEX_5X8 = const PrintJobMediaSize( + id: "NA_INDEX_5X8", widthMils: 5000, heightMils: 8000); + + ///North America Monarch media size: 7.25" x 10.5" (184mm x 267mm) + static const NA_MONARCH = const PrintJobMediaSize( + id: "NA_MONARCH", widthMils: 7250, heightMils: 10500); + + ///North America Quarto media size: 8" x 10" (203mm x 254mm) + static const NA_QUARTO = const PrintJobMediaSize( + id: "NA_QUARTO", widthMils: 8000, heightMils: 10000); + + ///North America Foolscap media size: 8" x 13" (203mm x 330mm) + static const NA_FOOLSCAP = const PrintJobMediaSize( + id: "NA_FOOLSCAP", widthMils: 8000, heightMils: 13000); + + ///North America ANSI C media size: 17" x 22" (432mm x 559mm) + static const ANSI_C = const PrintJobMediaSize( + id: "ANSI_C", widthMils: 17000, heightMils: 22000); + + ///North America ANSI D media size: 22" x 34" (559mm x 864mm) + static const ANSI_D = const PrintJobMediaSize( + id: "ANSI_D", widthMils: 22000, heightMils: 34000); + + ///North America ANSI E media size: 34" x 44" (864mm x 1118mm) + static const ANSI_E = const PrintJobMediaSize( + id: "ANSI_E", widthMils: 34000, heightMils: 44000); + + ///North America ANSI F media size: 28" x 40" (711mm x 1016mm) + static const ANSI_F = const PrintJobMediaSize( + id: "ANSI_F", widthMils: 28000, heightMils: 40000); + + ///North America Arch A media size: 9" x 12" (229mm x 305mm) + static const NA_ARCH_A = const PrintJobMediaSize( + id: "NA_ARCH_A", widthMils: 9000, heightMils: 12000); + + ///North America Arch B media size: 12" x 18" (305mm x 457mm) + static const NA_ARCH_B = const PrintJobMediaSize( + id: "NA_ARCH_B", widthMils: 12000, heightMils: 18000); + + ///North America Arch C media size: 18" x 24" (457mm x 610mm) + static const NA_ARCH_C = const PrintJobMediaSize( + id: "NA_ARCH_C", widthMils: 18000, heightMils: 24000); + + ///North America Arch D media size: 24" x 36" (610mm x 914mm) + static const NA_ARCH_D = const PrintJobMediaSize( + id: "NA_ARCH_D", widthMils: 24000, heightMils: 36000); + + ///North America Arch E media size: 36" x 48" (914mm x 1219mm) + static const NA_ARCH_E = const PrintJobMediaSize( + id: "NA_ARCH_E", widthMils: 36000, heightMils: 48000); + + ///North America Arch E1 media size: 30" x 42" (762mm x 1067mm) + static const NA_ARCH_E1 = const PrintJobMediaSize( + id: "NA_ARCH_E1", widthMils: 30000, heightMils: 42000); + + ///North America Super B media size: 13" x 19" (330mm x 483mm) + static const NA_SUPER_B = const PrintJobMediaSize( + id: "NA_SUPER_B", widthMils: 13000, heightMils: 19000); + + // Chinese + + ///Chinese ROC 8K media size: 270mm x 390mm (10.629" x 15.3543") + static const ROC_8K = const PrintJobMediaSize( + id: "ROC_8K", widthMils: 10629, heightMils: 15354); + + ///Chinese ROC 16K media size: 195mm x 270mm (7.677" x 10.629") + static const ROC_16K = const PrintJobMediaSize( + id: "ROC_16K", widthMils: 7677, heightMils: 10629); + + ///Chinese PRC 1 media size: 102mm x 165mm (4.015" x 6.496") + static const PRC_1 = + const PrintJobMediaSize(id: "PRC_1", widthMils: 4015, heightMils: 6496); + + ///Chinese PRC 2 media size: 102mm x 176mm (4.015" x 6.929") + static const PRC_2 = + const PrintJobMediaSize(id: "PRC_2", widthMils: 4015, heightMils: 6929); + + ///Chinese PRC 3 media size: 125mm x 176mm (4.921" x 6.929") + static const PRC_3 = + const PrintJobMediaSize(id: "PRC_3", widthMils: 4921, heightMils: 6929); + + ///Chinese PRC 4 media size: 110mm x 208mm (4.330" x 8.189") + static const PRC_4 = + const PrintJobMediaSize(id: "PRC_4", widthMils: 4330, heightMils: 8189); + + ///Chinese PRC 5 media size: 110mm x 220mm (4.330" x 8.661") + static const PRC_5 = + const PrintJobMediaSize(id: "PRC_5", widthMils: 4330, heightMils: 8661); + + ///Chinese PRC 6 media size: 120mm x 320mm (4.724" x 12.599") + static const PRC_6 = + const PrintJobMediaSize(id: "PRC_6", widthMils: 4724, heightMils: 12599); + + ///Chinese PRC 7 media size: 160mm x 230mm (6.299" x 9.055") + static const PRC_7 = + const PrintJobMediaSize(id: "PRC_7", widthMils: 6299, heightMils: 9055); + + ///Chinese PRC 8 media size: 120mm x 309mm (4.724" x 12.165") + static const PRC_8 = + const PrintJobMediaSize(id: "PRC_8", widthMils: 4724, heightMils: 12165); + + ///Chinese PRC 9 media size: 229mm x 324mm (9.016" x 12.756") + static const PRC_9 = + const PrintJobMediaSize(id: "PRC_9", widthMils: 9016, heightMils: 12756); + + ///Chinese PRC 10 media size: 324mm x 458mm (12.756" x 18.032") + static const PRC_10 = const PrintJobMediaSize( + id: "PRC_10", widthMils: 12756, heightMils: 18032); + + ///Chinese PRC 16k media size: 146mm x 215mm (5.749" x 8.465") + static const PRC_16K = + const PrintJobMediaSize(id: "PRC_16K", widthMils: 5749, heightMils: 8465); + + ///Chinese Pa Kai media size: 267mm x 389mm (10.512" x 15.315") + static const OM_PA_KAI = const PrintJobMediaSize( + id: "OM_PA_KAI", widthMils: 10512, heightMils: 15315); + + ///Chinese Dai Pa Kai media size: 275mm x 395mm (10.827" x 15.551") + static const OM_DAI_PA_KAI = const PrintJobMediaSize( + id: "OM_DAI_PA_KAI", widthMils: 10827, heightMils: 15551); + + ///Chinese Jurro Ku Kai media size: 198mm x 275mm (7.796" x 10.827") + static const OM_JUURO_KU_KAI = const PrintJobMediaSize( + id: "OM_JUURO_KU_KAI", widthMils: 7796, heightMils: 10827); + + // Japanese + + ///Japanese JIS B10 media size: 32mm x 45mm (1.259" x 1.772") + static const JIS_B10 = + const PrintJobMediaSize(id: "JIS_B10", widthMils: 1259, heightMils: 1772); + + ///Japanese JIS B9 media size: 45mm x 64mm (1.772" x 2.52") + static const JIS_B9 = + const PrintJobMediaSize(id: "JIS_B9", widthMils: 1772, heightMils: 2520); + + ///Japanese JIS B8 media size: 64mm x 91mm (2.52" x 3.583") + static const JIS_B8 = + const PrintJobMediaSize(id: "JIS_B8", widthMils: 2520, heightMils: 3583); + + ///Japanese JIS B7 media size: 91mm x 128mm (3.583" x 5.049") + static const JIS_B7 = + const PrintJobMediaSize(id: "JIS_B7", widthMils: 3583, heightMils: 5049); + + ///Japanese JIS B6 media size: 128mm x 182mm (5.049" x 7.165") + static const JIS_B6 = + const PrintJobMediaSize(id: "JIS_B6", widthMils: 5049, heightMils: 7165); + + ///Japanese JIS B5 media size: 182mm x 257mm (7.165" x 10.118") + static const JIS_B5 = + const PrintJobMediaSize(id: "JIS_B5", widthMils: 7165, heightMils: 10118); + + ///Japanese JIS B4 media size: 257mm x 364mm (10.118" x 14.331") + static const JIS_B4 = const PrintJobMediaSize( + id: "JIS_B4", widthMils: 10118, heightMils: 14331); + + ///Japanese JIS B3 media size: 364mm x 515mm (14.331" x 20.276") + static const JIS_B3 = const PrintJobMediaSize( + id: "JIS_B3", widthMils: 14331, heightMils: 20276); + + ///Japanese JIS B2 media size: 515mm x 728mm (20.276" x 28.661") + static const JIS_B2 = const PrintJobMediaSize( + id: "JIS_B2", widthMils: 20276, heightMils: 28661); + + ///Japanese JIS B1 media size: 728mm x 1030mm (28.661" x 40.551") + static const JIS_B1 = const PrintJobMediaSize( + id: "JIS_B1", widthMils: 28661, heightMils: 40551); + + ///Japanese JIS B0 media size: 1030mm x 1456mm (40.551" x 57.323") + static const JIS_B0 = const PrintJobMediaSize( + id: "JIS_B0", widthMils: 40551, heightMils: 57323); + + ///Japanese JIS Exec media size: 216mm x 330mm (8.504" x 12.992") + static const JIS_EXEC = const PrintJobMediaSize( + id: "JIS_EXEC", widthMils: 8504, heightMils: 12992); + + ///Japanese Chou4 media size: 90mm x 205mm (3.543" x 8.071") + static const JPN_CHOU4 = const PrintJobMediaSize( + id: "JPN_CHOU4", widthMils: 3543, heightMils: 8071); + + ///Japanese Chou3 media size: 120mm x 235mm (4.724" x 9.252") + static const JPN_CHOU3 = const PrintJobMediaSize( + id: "JPN_CHOU3", widthMils: 4724, heightMils: 9252); + + ///Japanese Chou2 media size: 111.1mm x 146mm (4.374" x 5.748") + static const JPN_CHOU2 = const PrintJobMediaSize( + id: "JPN_CHOU2", widthMils: 4374, heightMils: 5748); + + ///Japanese Hagaki media size: 100mm x 148mm (3.937" x 5.827") + static const JPN_HAGAKI = const PrintJobMediaSize( + id: "JPN_HAGAKI", widthMils: 3937, heightMils: 5827); + + ///Japanese Oufuku media size: 148mm x 200mm (5.827" x 7.874") + static const JPN_OUFUKU = const PrintJobMediaSize( + id: "JPN_OUFUKU", widthMils: 5827, heightMils: 7874); + + ///Japanese Kahu media size: 240mm x 322.1mm (9.449" x 12.681") + static const JPN_KAHU = const PrintJobMediaSize( + id: "JPN_KAHU", widthMils: 9449, heightMils: 12681); + + ///Japanese Kaku2 media size: 240mm x 332mm (9.449" x 13.071") + static const JPN_KAKU2 = const PrintJobMediaSize( + id: "JPN_KAKU2", widthMils: 9449, heightMils: 13071); + + ///Japanese You4 media size: 105mm x 235mm (4.134" x 9.252") + static const JPN_YOU4 = const PrintJobMediaSize( + id: "JPN_YOU4", widthMils: 4134, heightMils: 9252); + + ///Japanese Photo L media size: 89mm x 127mm (3.5 x 5") + static const JPN_OE_PHOTO_L = const PrintJobMediaSize( + id: "JPN_OE_PHOTO_L", widthMils: 3500, heightMils: 5000); +} diff --git a/lib/src/types/print_job_orientation.dart b/lib/src/types/print_job_orientation.dart new file mode 100644 index 00000000..0c6132bd --- /dev/null +++ b/lib/src/types/print_job_orientation.dart @@ -0,0 +1,52 @@ +import '../print_job/main.dart'; + +///Class representing the orientation of a [PrintJobController]. +class PrintJobOrientation { + final int _value; + + const PrintJobOrientation._internal(this._value); + + ///Set of all values of [PrintJobOrientation]. + static final Set values = [ + PrintJobOrientation.PORTRAIT, + PrintJobOrientation.LANDSCAPE, + ].toSet(); + + ///Gets a possible [PrintJobOrientation] instance from an [int] value. + static PrintJobOrientation? fromValue(int? value) { + if (value != null) { + try { + return PrintJobOrientation.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets [int] value. + int toValue() => _value; + + @override + String toString() { + switch (_value) { + case 1: + return "LANDSCAPE"; + case 0: + default: + return "PORTRAIT"; + } + } + + ///Pages are printed in portrait orientation. + static const PORTRAIT = const PrintJobOrientation._internal(0); + + ///Pages are printed in landscape orientation. + static const LANDSCAPE = const PrintJobOrientation._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} diff --git a/lib/src/types/print_job_output_type.dart b/lib/src/types/print_job_output_type.dart new file mode 100644 index 00000000..9df686a4 --- /dev/null +++ b/lib/src/types/print_job_output_type.dart @@ -0,0 +1,74 @@ +import '../print_job/main.dart'; + +///Class representing the kind of printable content of a [PrintJobController]. +class PrintJobOutputType { + final int _value; + + const PrintJobOutputType._internal(this._value); + + ///Set of all values of [PrintJobOutputType]. + static final Set values = [ + PrintJobOutputType.GENERAL, + PrintJobOutputType.PHOTO, + PrintJobOutputType.GRAYSCALE, + PrintJobOutputType.PHOTO_GRAYSCALE + ].toSet(); + + ///Gets a possible [PrintJobOutputType] instance from an [int] value. + static PrintJobOutputType? fromValue(int? value) { + if (value != null) { + try { + return PrintJobOutputType.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets [int] value. + int toValue() => _value; + + @override + String toString() { + switch (_value) { + case 1: + return "PHOTO"; + case 2: + return "GRAYSCALE"; + case 3: + return "PHOTO_GRAYSCALE"; + case 0: + default: + return "GENERAL"; + } + } + + ///Specifies that the printed content consists of mixed text, graphics, and images. + ///The default paper is Letter, A4, or similar locale-specific designation. + ///Output is normal quality, duplex. + static const GENERAL = const PrintJobOutputType._internal(0); + + ///Specifies that the printed content consists of black-and-white or color images. + ///The default paper is 4x6, A6, or similar locale-specific designation. + ///Output is high quality, simplex. + static const PHOTO = const PrintJobOutputType._internal(1); + + ///Specifies that the printed content is grayscale. + ///Set the output type to this value when your printable content contains no color—for example, it’s black text only. + ///The default paper is Letter/A4. Output is grayscale quality, duplex. + ///This content type can produce a performance improvement in some cases. + static const GRAYSCALE = const PrintJobOutputType._internal(2); + + ///Specifies that the printed content is a grayscale image. + ///Set the output type to this value when your printable content contains no color—for example, it’s black text only. + ///The default paper is Letter/A4. + ///Output is high quality grayscale, duplex. + static const PHOTO_GRAYSCALE = const PrintJobOutputType._internal(3); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} diff --git a/lib/src/types/print_job_rendering_quality.dart b/lib/src/types/print_job_rendering_quality.dart new file mode 100644 index 00000000..5a83105b --- /dev/null +++ b/lib/src/types/print_job_rendering_quality.dart @@ -0,0 +1,53 @@ +import '../print_job/main.dart'; + +///Class representing the rendering quality of a [PrintJobController]. +class PrintJobRenderingQuality { + final int _value; + + const PrintJobRenderingQuality._internal(this._value); + + ///Set of all values of [PrintJobRenderingQuality]. + static final Set values = [ + PrintJobRenderingQuality.BEST, + PrintJobRenderingQuality.RESPONSIVE, + ].toSet(); + + ///Gets a possible [PrintJobRenderingQuality] instance from an [int] value. + static PrintJobRenderingQuality? fromValue(int? value) { + if (value != null) { + try { + return PrintJobRenderingQuality.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets [int] value. + int toValue() => _value; + + @override + String toString() { + switch (_value) { + case 1: + return "RESPONSIVE"; + case 0: + default: + return "BEST"; + } + } + + ///Renders the printing at the best possible quality, regardless of speed. + static const BEST = const PrintJobRenderingQuality._internal(0); + + ///Sacrifices the least possible amount of rendering quality for speed to maintain a responsive user interface. + ///This option should be used only after establishing that best quality rendering does indeed make the user interface unresponsive. + static const RESPONSIVE = const PrintJobRenderingQuality._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} diff --git a/lib/src/types/print_job_resolution.dart b/lib/src/types/print_job_resolution.dart new file mode 100644 index 00000000..d0af8455 --- /dev/null +++ b/lib/src/types/print_job_resolution.dart @@ -0,0 +1,64 @@ +import '../print_job/main.dart'; + +///Class representing the supported resolution in DPI (dots per inch) for a [PrintJobController]. +///Resolution defines how many points with different color can be placed +///on one inch in horizontal or vertical direction of the target media. +///For example, a printer with 600 DPI can produce higher quality images +///the one with 300 DPI resolution. +class PrintJobResolution { + ///The unique resolution id. + /// + ///It is unique amongst other resolutions supported by the printer. + ///This id is defined by the client that generated the resolution + ///instance and should not be interpreted by other parties. + final String id; + + ///The human readable label. + final String label; + + ///The vertical resolution in DPI (dots per inch). + final int verticalDpi; + + ///The horizontal resolution in DPI (dots per inch). + final int horizontalDpi; + + const PrintJobResolution( + {required this.id, + required this.label, + required this.verticalDpi, + required this.horizontalDpi}); + + ///Gets a possible [PrintJobResolution] instance from a [Map] value. + static PrintJobResolution? fromMap(Map? map) { + if (map == null) { + return null; + } + + return PrintJobResolution( + id: map["id"], + label: map["label"], + verticalDpi: map["verticalDpi"], + horizontalDpi: map["horizontalDpi"], + ); + } + + ///Converts instance to a map. + Map toMap() { + return { + "id": id, + "label": label, + "verticalDpi": verticalDpi, + "horizontalDpi": horizontalDpi, + }; + } + + ///Converts instance to a map. + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/src/types/print_job_state.dart b/lib/src/types/print_job_state.dart new file mode 100644 index 00000000..ca0b461f --- /dev/null +++ b/lib/src/types/print_job_state.dart @@ -0,0 +1,123 @@ +import '../print_job/main.dart'; + +///Class representing the state of a [PrintJobController]. +class PrintJobState { + final int _value; + + const PrintJobState._internal(this._value); + + ///Set of all values of [PrintJobState]. + static final Set values = [ + PrintJobState.CREATED, + PrintJobState.QUEUED, + PrintJobState.STARTED, + PrintJobState.BLOCKED, + PrintJobState.COMPLETED, + PrintJobState.FAILED, + PrintJobState.CANCELED, + ].toSet(); + + ///Gets a possible [PrintJobState] instance from an [int] value. + static PrintJobState? fromValue(int? value) { + if (value != null) { + try { + return PrintJobState.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets [int] value. + int toValue() => _value; + + @override + String toString() { + switch (_value) { + case 2: + return "QUEUED"; + case 3: + return "STARTED"; + case 4: + return "BLOCKED"; + case 5: + return "COMPLETED"; + case 6: + return "FAILED"; + case 7: + return "CANCELED"; + case 1: + default: + return "CREATED"; + } + } + + ///Print job state: The print job is being created but not yet ready to be printed. + /// + ///Next valid states: [QUEUED]. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJobInfo.STATE_CREATED](https://developer.android.com/reference/android/print/PrintJobInfo#STATE_CREATED)) + ///- iOS + static const CREATED = const PrintJobState._internal(1); + + ///Print job state: The print jobs is created, it is ready to be printed and should be processed. + /// + ///Next valid states: [STARTED], [FAILED], [CANCELED]. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJobInfo.STATE_QUEUED](https://developer.android.com/reference/android/print/PrintJobInfo#STATE_QUEUED)) + static const QUEUED = const PrintJobState._internal(2); + + ///Print job state: The print job is being printed. + /// + ///Next valid states: [COMPLETED], [FAILED], [CANCELED], [BLOCKED]. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJobInfo.STATE_STARTED](https://developer.android.com/reference/android/print/PrintJobInfo#STATE_STARTED)) + ///- iOS + static const STARTED = const PrintJobState._internal(3); + + ///Print job state: The print job is blocked. + /// + ///Next valid states: [FAILED], [CANCELED], [STARTED]. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJobInfo.STATE_BLOCKED](https://developer.android.com/reference/android/print/PrintJobInfo#STATE_BLOCKED)) + static const BLOCKED = const PrintJobState._internal(4); + + ///Print job state: The print job is successfully printed. This is a terminal state. + /// + ///Next valid states: None. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJobInfo.STATE_COMPLETED](https://developer.android.com/reference/android/print/PrintJobInfo#STATE_COMPLETED)) + ///- iOS + static const COMPLETED = const PrintJobState._internal(5); + + + ///Print job state: The print job was printing but printing failed. + /// + ///Next valid states: None. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJobInfo.STATE_FAILED](https://developer.android.com/reference/android/print/PrintJobInfo#STATE_FAILED)) + ///- iOS + static const FAILED = const PrintJobState._internal(6); + + ///Print job state: The print job is canceled. This is a terminal state. + /// + ///Next valid states: None. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - PrintJobInfo.STATE_CANCELED](https://developer.android.com/reference/android/print/PrintJobInfo#STATE_CANCELED)) + ///- iOS + static const CANCELED = const PrintJobState._internal(7); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} diff --git a/lib/src/types/ssl_error_type.dart b/lib/src/types/ssl_error_type.dart index b722e3e8..11baaa28 100644 --- a/lib/src/types/ssl_error_type.dart +++ b/lib/src/types/ssl_error_type.dart @@ -38,7 +38,7 @@ class SslErrorType { return null; } - ///Gets a possible [SslErrorType] instance from a value. + ///Gets a possible [SslErrorType] instance from a native value. static SslErrorType? fromNativeValue(int? value) { if (value != null) { try { diff --git a/lib/src/types/web_resource_error_type.dart b/lib/src/types/web_resource_error_type.dart index e23058b9..1e4bad0a 100644 --- a/lib/src/types/web_resource_error_type.dart +++ b/lib/src/types/web_resource_error_type.dart @@ -79,7 +79,7 @@ class WebResourceErrorType { return null; } - ///Gets a possible [WebResourceErrorType] instance from an [int] value. + ///Gets a possible [WebResourceErrorType] instance from an [int] native value. static WebResourceErrorType? fromNativeValue(int? value) { if (value != null) { try { diff --git a/lib/src/util.dart b/lib/src/util.dart index 2af57e03..53e60557 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -502,3 +502,21 @@ extension MapSize on Size { return {'width': width, 'height': height}; } } + +extension MapEdgeInsets on EdgeInsets { + static EdgeInsets? fromMap(Map? map) { + if (map == null) { + return null; + } + return EdgeInsets.fromLTRB( + map['left'], map['top'], map['right'], map['bottom']); + } + + Map toJson() { + return toMap(); + } + + Map toMap() { + return {'top': top, 'right': right, 'bottom': bottom, 'left': left}; + } +} diff --git a/lib/src/web/in_app_web_view_web_element.dart b/lib/src/web/in_app_web_view_web_element.dart index fa5717a5..cfea9c4b 100644 --- a/lib/src/web/in_app_web_view_web_element.dart +++ b/lib/src/web/in_app_web_view_web_element.dart @@ -157,6 +157,7 @@ class InAppWebViewWebElement { 'flutter_inappwebview for web doesn\'t implement \'${call.method}\'', ); } + return null; } void prepare() { @@ -502,10 +503,13 @@ class InAppWebViewWebElement { await _channel?.invokeMethod("onWindowBlur"); } - void onPrint(String? url) async { - var obj = {"url": url}; + void onPrintRequest(String? url) async { + var obj = { + "url": url, + "printJobId": null + }; - await _channel?.invokeMethod("onPrint", obj); + await _channel?.invokeMethod("onPrintRequest", obj); } void onEnterFullscreen() async { diff --git a/lib/src/web/web_platform.dart b/lib/src/web/web_platform.dart index 1ebd36d8..7ebef044 100644 --- a/lib/src/web/web_platform.dart +++ b/lib/src/web/web_platform.dart @@ -82,9 +82,9 @@ Future _dartNativeCommunication(String method, dynamic viewId, case 'onWindowBlur': webViewHtmlElement.onWindowBlur(); break; - case 'onPrint': + case 'onPrintRequest': String? url = args![0]; - webViewHtmlElement.onPrint(url); + webViewHtmlElement.onPrintRequest(url); break; case 'onEnterFullscreen': webViewHtmlElement.onEnterFullscreen();