From 955a75ec69bcdf132cdbabb27492b79f0a82274e Mon Sep 17 00:00:00 2001 From: Andri Yoga Date: Fri, 3 Dec 2021 19:12:24 +0700 Subject: [PATCH 1/2] fix: add native code to convert content image, remove webcontent_converter.dart --- CHANGELOG.md | 4 + .../blue_print_pos/BluePrintPosPlugin.kt | 175 ++++++++++++++--- .../blue_print_pos/FLNativeViewFactory.kt | 43 +++++ example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Flutter/Debug.xcconfig | 2 +- example/ios/Flutter/Release.xcconfig | 2 +- example/ios/Podfile.lock | 16 +- example/ios/Runner.xcodeproj/project.pbxproj | 3 +- example/lib/main.dart | 176 +++++++++--------- example/pubspec.lock | 2 +- ios/Classes/FLWebView.swift | 82 ++++++++ ios/Classes/SwiftBluePrintPosPlugin.swift | 103 +++++++++- lib/blue_print_pos.dart | 26 ++- .../webcontent_converter.dart | 44 ----- pubspec.lock | 10 +- pubspec.yaml | 2 +- 16 files changed, 510 insertions(+), 182 deletions(-) create mode 100644 android/src/main/kotlin/com/ayeee/blue_print_pos/FLNativeViewFactory.kt create mode 100644 ios/Classes/FLWebView.swift delete mode 100644 lib/webcontent_converter/webcontent_converter.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index c13289f..1085941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.8 + +* Add parameter option to generate image as raster + ## 0.0.7 * Add parameter option to generate image as raster diff --git a/android/src/main/kotlin/com/ayeee/blue_print_pos/BluePrintPosPlugin.kt b/android/src/main/kotlin/com/ayeee/blue_print_pos/BluePrintPosPlugin.kt index c3f70c4..0b168a6 100644 --- a/android/src/main/kotlin/com/ayeee/blue_print_pos/BluePrintPosPlugin.kt +++ b/android/src/main/kotlin/com/ayeee/blue_print_pos/BluePrintPosPlugin.kt @@ -1,36 +1,161 @@ package com.ayeee.blue_print_pos +import android.app.Activity +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.view.View +import android.view.WindowInsets +import android.webkit.WebView +import android.webkit.WebViewClient import androidx.annotation.NonNull - import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry.Registrar +import java.io.ByteArrayOutputStream +import kotlin.math.absoluteValue /** BluePrintPosPlugin */ -class BluePrintPosPlugin: FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel - - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "blue_print_pos") - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - if (call.method == "getPlatformVersion") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() - } - } - - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } +class BluePrintPosPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel + private lateinit var activity: Activity + private lateinit var context: Context + private lateinit var webView: WebView + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + val viewID = "webview-view-type" + flutterPluginBinding.platformViewRegistry.registerViewFactory(viewID, FLNativeViewFactory()) + + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "blue_print_pos") + channel.setMethodCallHandler(this) + context = flutterPluginBinding.applicationContext + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + val arguments = call.arguments as Map<*, *> + val content = arguments["content"] as String + val duration = arguments["duration"] as Double? + + if (call.method == "contentToImage") { + webView = WebView(this.context) + val dWidth: Int + val dHeight: Int + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val windowMetrics = activity.windowManager.currentWindowMetrics + val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) + dWidth = windowMetrics.bounds.width() - insets.left - insets.right + dHeight = windowMetrics.bounds.height() - insets.bottom - insets.top + } else { + dWidth = this.activity.window.windowManager.defaultDisplay.width + dHeight = this.activity.window.windowManager.defaultDisplay.height + } + print("\ndwidth : $dWidth") + print("\ndheight : $dHeight") + webView.layout(0, 0, dWidth, dHeight) + webView.loadDataWithBaseURL(null, content, "text/HTML", "UTF-8", null) + webView.setInitialScale(1) + webView.settings.javaScriptEnabled = true + webView.settings.useWideViewPort = true + webView.settings.javaScriptCanOpenWindowsAutomatically = true + webView.settings.loadWithOverviewMode = true + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + print("\n=======> enabled scrolled <=========") + WebView.enableSlowWholeDocumentDraw() + } + + print("\n ///////////////// webview setted /////////////////") + + webView.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + + Handler(Looper.getMainLooper()).postDelayed({ + print("\nOS Version: ${Build.VERSION.SDK_INT}") + print("\n ================ webview completed ==============") + print("\n scroll delayed ${webView.scrollBarFadeDuration}") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript("document.body.offsetWidth") { offsetWidth -> + webView.evaluateJavascript("document.body.offsetHeight") { offsetHeight -> + print("\noffsetWidth : $offsetWidth") + print("\noffsetHeight : $offsetHeight") + val data = webView.toBitmap( + offsetWidth!!.toDouble(), + offsetHeight!!.toDouble() + ) + if (data != null) { + val bytes = data.toByteArray() + result.success(bytes) + println("\n Got snapshot") + } + } + } + } + }, duration!!.toLong()) + } + } + } else { + result.notImplemented() + } + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + print("onAttachedToActivity") + activity = binding.activity + webView = WebView(activity.applicationContext) + webView.minimumHeight = 1 + webView.minimumWidth = 1 + } + + override fun onDetachedFromActivityForConfigChanges() { + // TODO: the Activity your plugin was attached to was destroyed to change configuration. + // This call will be followed by onReattachedToActivityForConfigChanges(). + print("onDetachedFromActivityForConfigChanges") + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + // TODO: your plugin is now attached to a new Activity after a configuration change. + print("onAttachedToActivity") + onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + // TODO: your plugin is no longer associated with an Activity. Clean up references. + print("onDetachedFromActivity") + } + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} + +fun WebView.toBitmap(offsetWidth: Double, offsetHeight: Double): Bitmap? { + if (offsetHeight > 0 && offsetWidth > 0) { + val width = (offsetWidth * this.resources.displayMetrics.density).absoluteValue.toInt() + val height = (offsetHeight * this.resources.displayMetrics.density).absoluteValue.toInt() + this.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)) + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + this.draw(canvas) + return bitmap + } + return null +} + +fun Bitmap.toByteArray(): ByteArray { + ByteArrayOutputStream().apply { + compress(Bitmap.CompressFormat.PNG, 100, this) + return toByteArray() + } } diff --git a/android/src/main/kotlin/com/ayeee/blue_print_pos/FLNativeViewFactory.kt b/android/src/main/kotlin/com/ayeee/blue_print_pos/FLNativeViewFactory.kt new file mode 100644 index 0000000..6dc8656 --- /dev/null +++ b/android/src/main/kotlin/com/ayeee/blue_print_pos/FLNativeViewFactory.kt @@ -0,0 +1,43 @@ +package com.ayeee.blue_print_pos + +import android.content.Context +import android.view.View +import android.webkit.WebView +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + +class FLNativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + override fun create(context: Context, viewId: Int, args: Any?): PlatformView { + val creationParams = args as Map? + return FLNativeView(context, viewId, creationParams) + } +} + + +internal class FLNativeView(context: Context, id: Int, creationParams: Map?) : PlatformView { + private val webView: WebView = WebView(context) + private var arguments: Map? = creationParams + + override fun getView(): View { + return webView + } + + override fun dispose() {} + + init { + var width = (arguments!!["width"]!! as Number).toInt() + var height = (arguments!!["height"]!! as Number).toInt() + var content = arguments!!["content"] as String + webView.layout(0, 0, width, height) + webView.loadDataWithBaseURL(null, content, "text/HTML", "UTF-8", null) + webView.setInitialScale(1) + webView.settings.javaScriptEnabled = true + webView.settings.useWideViewPort = true + webView.settings.javaScriptCanOpenWindowsAutomatically = true + webView.settings.loadWithOverviewMode = true + + + } + +} diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9367d48..8d4492f 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index ec97fc6..e8efba1 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index c4855bf..399e934 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f9a4a2e..6fe0f4e 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,6 +1,8 @@ PODS: - blue_print_pos (0.0.1): - Flutter + - blue_thermal_printer (0.0.1): + - Flutter - Flutter (1.0.0) - flutter_blue (0.0.1): - Flutter @@ -9,14 +11,12 @@ PODS: - Flutter - Protobuf (~> 3.11.4) - Protobuf (3.11.4) - - webcontent_converter (0.0.1): - - Flutter DEPENDENCIES: - blue_print_pos (from `.symlinks/plugins/blue_print_pos/ios`) + - blue_thermal_printer (from `.symlinks/plugins/blue_thermal_printer/ios`) - Flutter (from `Flutter`) - flutter_blue (from `.symlinks/plugins/flutter_blue/ios`) - - webcontent_converter (from `.symlinks/plugins/webcontent_converter/ios`) SPEC REPOS: trunk: @@ -25,20 +25,20 @@ SPEC REPOS: EXTERNAL SOURCES: blue_print_pos: :path: ".symlinks/plugins/blue_print_pos/ios" + blue_thermal_printer: + :path: ".symlinks/plugins/blue_thermal_printer/ios" Flutter: :path: Flutter flutter_blue: :path: ".symlinks/plugins/flutter_blue/ios" - webcontent_converter: - :path: ".symlinks/plugins/webcontent_converter/ios" SPEC CHECKSUMS: blue_print_pos: 23e8b4bede0fc07cd75b3257783523ca332bf3cb - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + blue_thermal_printer: e0f989c1ba2cae289f6472ebae0a22611958f2bc + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a flutter_blue: eeb381dc4727a0954dede73515f683865494b370 Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 - webcontent_converter: baa47c5fc5a9e8d703897a0b128f969c58fd2c7f PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d -COCOAPODS: 1.10.1 +COCOAPODS: 1.10.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 2c6285e..cf8ab8a 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -121,7 +121,6 @@ 4CEB8AF9736A2F8D716546E1 /* Pods-Runner.release.xcconfig */, E5D7780485383C04BB7F6B16 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; diff --git a/example/lib/main.dart b/example/lib/main.dart index 73e0690..9ae1b37 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -38,103 +38,105 @@ class _MyAppState extends State { ), ) : _blueDevices.isNotEmpty - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: List.generate(_blueDevices.length, - (int index) { - return Row( - children: [ - Expanded( - child: GestureDetector( - onTap: _blueDevices[index].address == - (_selectedDevice?.address ?? '') - ? _onDisconnectDevice - : () => _onSelectDevice(index), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - _blueDevices[index].name, - style: TextStyle( - color: _selectedDevice?.address == - _blueDevices[index] - .address - ? Colors.blue - : Colors.black, - fontSize: 20, - fontWeight: FontWeight.w500, + ? SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: List.generate(_blueDevices.length, + (int index) { + return Row( + children: [ + Expanded( + child: GestureDetector( + onTap: _blueDevices[index].address == + (_selectedDevice?.address ?? '') + ? _onDisconnectDevice + : () => _onSelectDevice(index), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + _blueDevices[index].name, + style: TextStyle( + color: _selectedDevice?.address == + _blueDevices[index] + .address + ? Colors.blue + : Colors.black, + fontSize: 20, + fontWeight: FontWeight.w500, + ), ), - ), - Text( - _blueDevices[index].address, - style: TextStyle( - color: _selectedDevice?.address == - _blueDevices[index] - .address - ? Colors.blueGrey - : Colors.grey, - fontSize: 14, - fontWeight: FontWeight.w500, + Text( + _blueDevices[index].address, + style: TextStyle( + color: _selectedDevice?.address == + _blueDevices[index] + .address + ? Colors.blueGrey + : Colors.grey, + fontSize: 14, + fontWeight: FontWeight.w500, + ), ), - ), - ], + ], + ), ), ), ), - ), - if (_loadingAtIndex == index && _isLoading) - Container( - height: 24.0, - width: 24.0, - margin: const EdgeInsets.only(right: 8.0), - child: const CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation( - Colors.blue, + if (_loadingAtIndex == index && _isLoading) + Container( + height: 24.0, + width: 24.0, + margin: const EdgeInsets.only(right: 8.0), + child: const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Colors.blue, + ), ), ), - ), - if (!_isLoading && - _blueDevices[index].address == - (_selectedDevice?.address ?? '')) - TextButton( - onPressed: _onPrintReceipt, - child: Container( - color: _selectedDevice == null - ? Colors.grey - : Colors.blue, - padding: const EdgeInsets.all(8.0), - child: const Text( - 'Test Print', - style: TextStyle(color: Colors.white), + if (!_isLoading && + _blueDevices[index].address == + (_selectedDevice?.address ?? '')) + TextButton( + onPressed: _onPrintReceipt, + child: Container( + color: _selectedDevice == null + ? Colors.grey + : Colors.blue, + padding: const EdgeInsets.all(8.0), + child: const Text( + 'Test Print', + style: TextStyle(color: Colors.white), + ), ), - ), - style: ButtonStyle( - backgroundColor: MaterialStateProperty - .resolveWith( - (Set states) { - if (states.contains( - MaterialState.pressed)) { - return Theme.of(context) - .colorScheme - .primary - .withOpacity(0.5); - } - return Theme.of(context).primaryColor; - }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty + .resolveWith( + (Set states) { + if (states.contains( + MaterialState.pressed)) { + return Theme.of(context) + .colorScheme + .primary + .withOpacity(0.5); + } + return Theme.of(context).primaryColor; + }, + ), ), ), - ), - ], - ); - }), - ), - ], - ) + ], + ); + }), + ), + ], + ), + ) : Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/example/pubspec.lock b/example/pubspec.lock index edd5e98..5012899 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,7 +21,7 @@ packages: path: ".." relative: true source: path - version: "0.0.5" + version: "0.0.7" blue_thermal_printer: dependency: transitive description: diff --git a/ios/Classes/FLWebView.swift b/ios/Classes/FLWebView.swift new file mode 100644 index 0000000..a268cfa --- /dev/null +++ b/ios/Classes/FLWebView.swift @@ -0,0 +1,82 @@ +import Flutter +import UIKit +import WebKit + +class FLNativeViewFactory: NSObject, FlutterPlatformViewFactory { + private var messenger: FlutterBinaryMessenger + + init(messenger: FlutterBinaryMessenger) { + self.messenger = messenger + super.init() + } + + /// add this to receive args + public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { + return FlutterStandardMessageCodec.sharedInstance() + } + + func create( + withFrame frame: CGRect, + viewIdentifier viewId: Int64, + arguments args: Any? + ) -> FlutterPlatformView { + print("frame \(frame)") + return FLNativeView( + frame: frame, + viewIdentifier: viewId, + arguments: args, + binaryMessenger: messenger) + } +} + +class FLNativeView: NSObject, FlutterPlatformView { + private var _frame: CGRect? + private var _view: UIView? + private var _arguments : Dictionary? + private var _webView : WKWebView? + + init( + frame: CGRect, + viewIdentifier viewId: Int64, + arguments args: Any?, + binaryMessenger messenger: FlutterBinaryMessenger? + ) { + _arguments = args as? Dictionary + let width = _arguments!["width"]! as! Double + let height = _arguments!["height"]! as! Double + _frame = CGRect(x: 0, y: 0, width: width, height: height ) + _view = UIView(frame: _frame!) + print("init view.width \(_view!.frame.width)") + print("init view.height \(_view!.frame.height)") + print("init view.bounds \(_view?.bounds)") + _view!.clipsToBounds = true + let configuration = WKWebViewConfiguration() + _webView = WKWebView(frame: _view!.bounds, configuration: configuration) + _webView!.tag = 100 + _webView!.scrollView.bounces = true + super.init() + // iOS views can be created here + createNativeView(view: _view!) + } + + func view() -> UIView { + return _view! + } + + func createNativeView(view _view: UIView){ + let content = _arguments!["content"]! as! String + _webView!.loadHTMLString(content, baseURL: Bundle.main.resourceURL) + _view.addSubview(_webView!) + } + + deinit { + self.dispose() + } + + func dispose() { + print("dispose") + _arguments = nil + _webView = nil + _view = nil + } +} diff --git a/ios/Classes/SwiftBluePrintPosPlugin.swift b/ios/Classes/SwiftBluePrintPosPlugin.swift index d1f7194..2c93b24 100644 --- a/ios/Classes/SwiftBluePrintPosPlugin.swift +++ b/ios/Classes/SwiftBluePrintPosPlugin.swift @@ -1,14 +1,109 @@ import Flutter import UIKit +import WebKit public class SwiftBluePrintPosPlugin: NSObject, FlutterPlugin { + var webView : WKWebView! + var urlObservation: NSKeyValueObservation? + public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "blue_print_pos", binaryMessenger: registrar.messenger()) - let instance = SwiftBluePrintPosPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) + let channel = FlutterMethodChannel(name: "blue_print_pos", binaryMessenger: registrar.messenger()) + let instance = SwiftBluePrintPosPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + + // binding native view to flutter widget + let viewID = "webview-view-type" + let factory = FLNativeViewFactory(messenger: registrar.messenger()) + registrar.register(factory, withId: viewID) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - result("iOS " + UIDevice.current.systemVersion) + let method = call.method + let arguments = call.arguments as? [String: Any] + let content = arguments!["content"] as? String + var duration = arguments!["duration"] as? Double + if (duration==nil) { duration = 2000.0 } + switch method { + case "contentToImage": + self.webView = WKWebView() + self.webView.isHidden = true + self.webView.tag = 100 + self.webView.loadHTMLString(content!, baseURL: Bundle.main.resourceURL)// load html into hidden webview + var bytes = FlutterStandardTypedData.init(bytes: Data() ) + urlObservation = webView.observe(\.isLoading, changeHandler: { (webView, change) in + DispatchQueue.main.asyncAfter(deadline: .now() + (duration!/10000) ) { + print("height = \(self.webView.scrollView.contentSize.height)") + print("width = \(self.webView.scrollView.contentSize.width)") + if #available(iOS 11.0, *) { + let configuration = WKSnapshotConfiguration() + configuration.rect = CGRect(origin: .zero, size: (self.webView.scrollView.contentSize)) + self.webView.snapshotView(afterScreenUpdates: true) + self.webView.takeSnapshot(with: configuration) { (image, error) in + guard let data = image!.jpegData(compressionQuality: 1) else { + result( bytes ) + self.dispose() + return + } + bytes = FlutterStandardTypedData.init(bytes: data) + result(bytes) + self.dispose() + print("Got snapshot") + } + } else if #available(iOS 9.0, *) { + + let image = self.webView.snapshot() + guard let data = image!.jpegData(compressionQuality: 1) else { + result( bytes ) + self.dispose() + return + } + bytes = FlutterStandardTypedData.init(bytes: data) + result(bytes) + self.dispose() + print("Got snapshot") + + + } else { + + result( bytes ) + self.dispose() + } + + } + }) + + break + default: + result("iOS " + UIDevice.current.systemVersion) + } + } + + func dispose() { + //dispose + if let viewWithTag = self.webView.viewWithTag(100) { + viewWithTag.removeFromSuperview() // remove hidden webview when pdf is generated + // clear WKWebView cache + if #available(iOS 9.0, *) { + WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in + records.forEach { record in + WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {}) + } + } + } + } + self.webView = nil } } + +// WKWebView extension for export web html content into pdf +extension WKWebView { + + func snapshot() -> UIImage? + { + UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0); + self.drawHierarchy(in: self.bounds, afterScreenUpdates: true); + let snapshotImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return snapshotImage; + } +} diff --git a/lib/blue_print_pos.dart b/lib/blue_print_pos.dart index 7fa5e91..b78f6dc 100644 --- a/lib/blue_print_pos.dart +++ b/lib/blue_print_pos.dart @@ -1,3 +1,4 @@ +import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui'; @@ -6,9 +7,9 @@ import 'package:blue_print_pos/models/connection_status.dart'; import 'package:blue_print_pos/models/models.dart'; import 'package:blue_print_pos/receipt/receipt_section_text.dart'; import 'package:blue_print_pos/scanner/blue_scanner.dart'; -import 'package:blue_print_pos/webcontent_converter/webcontent_converter.dart'; import 'package:blue_thermal_printer/blue_thermal_printer.dart' as blue_thermal; import 'package:esc_pos_utils_plus/esc_pos_utils.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_blue/flutter_blue.dart' as flutter_blue; import 'package:flutter_blue/gen/flutterblue.pb.dart' as proto; import 'package:image/image.dart' as img; @@ -22,6 +23,8 @@ class BluePrintPos { static BluePrintPos get instance => BluePrintPos._(); + static const MethodChannel _channel = MethodChannel('blue_print_pos'); + /// This field is library to handle in Android Platform blue_thermal.BlueThermalPrinter? _bluetoothAndroid; @@ -118,7 +121,7 @@ class BluePrintPos { bool useCut = false, bool useRaster = false, }) async { - final Uint8List bytes = await WebcontentConverter.contentToImage( + final Uint8List bytes = await contentToImage( content: receiptSectionText.content); final List byteBuffer = await _getBytes( bytes, @@ -258,4 +261,23 @@ class BluePrintPos { rethrow; } } + + static Future contentToImage({ + required String content, + double duration = 2000, + }) async { + final Map arguments = { + 'content': content, + 'duration': duration + }; + Uint8List results = Uint8List.fromList([]); + try { + results = await _channel.invokeMethod('contentToImage', arguments) ?? + Uint8List.fromList([]); + } on Exception catch (e) { + log('[method:contentToImage]: $e'); + throw Exception('Error: $e'); + } + return results; + } } diff --git a/lib/webcontent_converter/webcontent_converter.dart b/lib/webcontent_converter/webcontent_converter.dart deleted file mode 100644 index 79870b9..0000000 --- a/lib/webcontent_converter/webcontent_converter.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:async'; -import 'dart:developer'; -import 'dart:typed_data'; -import 'package:flutter/services.dart'; - -/// [WebcontentConverter] will convert into raw bytes image -class WebcontentConverter { - WebcontentConverter._(); - - static const MethodChannel _channel = MethodChannel('webcontent_converter'); - - /// ## `WebcontentConverter.contentToImage` - /// `This method use html content directly to convert html to List image` - /// ### Example: - /// ``` - /// final content = Demo.getReceiptContent(); - /// var bytes = await WebcontentConverter.contentToImage(content: content); - /// if (bytes.length > 0){ - /// var dir = await getTemporaryDirectory(); - /// var path = join(dir.path, "receipt.jpg"); - /// File file = File(path); - /// await file.writeAsBytes(bytes); - /// } - /// ``` - static Future contentToImage({ - required String content, - double duration = 2000, - String? executablePath, - }) async { - final Map arguments = { - 'content': content, - 'duration': duration - }; - Uint8List results = Uint8List.fromList([]); - try { - results = await _channel.invokeMethod('contentToImage', arguments) ?? - Uint8List.fromList([]); - } on Exception catch (e) { - log('[method:contentToImage]: $e'); - throw Exception('Error: $e'); - } - return results; - } -} diff --git a/pubspec.lock b/pubspec.lock index 6465dc0..c42080e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -14,7 +14,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.5.0" blue_thermal_printer: dependency: "direct main" description: @@ -42,7 +42,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.2.0" clock: dependency: transitive description: @@ -150,7 +150,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.3.0" path: dependency: transitive description: @@ -204,7 +204,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.0" stack_trace: dependency: transitive description: @@ -239,7 +239,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.2.19" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 33ad569..e969491 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: blue_print_pos description: Plugin to connecting bluetooth printer device, support on Android and iOS -version: 0.0.7 +version: 0.0.8 homepage: https://github.com/andriyoganp/blue_print_pos environment: From a893ee0a36cafb8994023efe416ae06f435f318a Mon Sep 17 00:00:00 2001 From: Andri Yoga Date: Fri, 3 Dec 2021 19:13:03 +0700 Subject: [PATCH 2/2] chore: update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1085941..17e09f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.0.8 -* Add parameter option to generate image as raster +* Add native code to convert content image ## 0.0.7