David PHAM-VAN

Add iOS Direct Print

1 # Changelog 1 # Changelog
2 2
  3 +## 2.1.7
  4 +
  5 +- Add iOS Direct Print
  6 +
3 ## 2.1.6 7 ## 2.1.6
4 8
5 - Add qrcode to example 9 - Add qrcode to example
@@ -26,8 +26,11 @@ class MyApp extends StatefulWidget { @@ -26,8 +26,11 @@ class MyApp extends StatefulWidget {
26 26
27 class MyAppState extends State<MyApp> { 27 class MyAppState extends State<MyApp> {
28 final GlobalKey<State<StatefulWidget>> shareWidget = GlobalKey(); 28 final GlobalKey<State<StatefulWidget>> shareWidget = GlobalKey();
  29 + final GlobalKey<State<StatefulWidget>> pickWidget = GlobalKey();
29 final GlobalKey<State<StatefulWidget>> previewContainer = GlobalKey(); 30 final GlobalKey<State<StatefulWidget>> previewContainer = GlobalKey();
30 31
  32 + Printer selectedPrinter;
  33 +
31 Future<void> _printPdf() async { 34 Future<void> _printPdf() async {
32 print('Print ...'); 35 print('Print ...');
33 final bool result = await Printing.layoutPdf( 36 final bool result = await Printing.layoutPdf(
@@ -50,6 +53,36 @@ class MyAppState extends State<MyApp> { @@ -50,6 +53,36 @@ class MyAppState extends State<MyApp> {
50 ); 53 );
51 } 54 }
52 55
  56 + Future<void> _pickPrinter() async {
  57 + print('Pick printer ...');
  58 +
  59 + // Calculate the widget center for iPad sharing popup position
  60 + final RenderBox referenceBox = pickWidget.currentContext.findRenderObject();
  61 + final Offset topLeft =
  62 + referenceBox.localToGlobal(referenceBox.paintBounds.topLeft);
  63 + final Offset bottomRight =
  64 + referenceBox.localToGlobal(referenceBox.paintBounds.bottomRight);
  65 + final Rect bounds = Rect.fromPoints(topLeft, bottomRight);
  66 +
  67 + final Printer printer = await Printing.pickPrinter(bounds: bounds);
  68 +
  69 + setState(() {
  70 + selectedPrinter = printer;
  71 + });
  72 +
  73 + print('Selected printer: $selectedPrinter');
  74 + }
  75 +
  76 + Future<void> _directPrintPdf() async {
  77 + print('Direct print ...');
  78 + final bool result = await Printing.directPrintPdf(
  79 + printer: selectedPrinter,
  80 + onLayout: (PdfPageFormat format) async =>
  81 + (await generateDocument(PdfPageFormat.letter)).save());
  82 +
  83 + print('Document printed: $result');
  84 + }
  85 +
53 Future<void> _sharePdf() async { 86 Future<void> _sharePdf() async {
54 print('Share ...'); 87 print('Share ...');
55 final pdf.Document document = await generateDocument(PdfPageFormat.a4); 88 final pdf.Document document = await generateDocument(PdfPageFormat.a4);
@@ -124,6 +157,22 @@ class MyAppState extends State<MyApp> { @@ -124,6 +157,22 @@ class MyAppState extends State<MyApp> {
124 children: <Widget>[ 157 children: <Widget>[
125 RaisedButton( 158 RaisedButton(
126 child: const Text('Print Document'), onPressed: _printPdf), 159 child: const Text('Print Document'), onPressed: _printPdf),
  160 + Row(
  161 + mainAxisSize: MainAxisSize.min,
  162 + children: <Widget>[
  163 + RaisedButton(
  164 + key: pickWidget,
  165 + child: const Text('Pick Printer'),
  166 + onPressed: _pickPrinter),
  167 + const SizedBox(width: 10),
  168 + RaisedButton(
  169 + child: Text(selectedPrinter == null
  170 + ? 'Direct Print'
  171 + : 'Print to $selectedPrinter'),
  172 + onPressed:
  173 + selectedPrinter != null ? _directPrintPdf : null),
  174 + ],
  175 + ),
127 RaisedButton( 176 RaisedButton(
128 key: shareWidget, 177 key: shareWidget,
129 child: const Text('Share Document'), 178 child: const Text('Share Document'),
@@ -37,7 +37,9 @@ class PdfPrintPageRenderer: UIPrintPageRenderer { @@ -37,7 +37,9 @@ class PdfPrintPageRenderer: UIPrintPageRenderer {
37 let page = pdfDocument?.page(at: pageIndex + 1) 37 let page = pdfDocument?.page(at: pageIndex + 1)
38 ctx?.scaleBy(x: 1.0, y: -1.0) 38 ctx?.scaleBy(x: 1.0, y: -1.0)
39 ctx?.translateBy(x: 0.0, y: -paperRect.size.height) 39 ctx?.translateBy(x: 0.0, y: -paperRect.size.height)
40 - ctx?.drawPDFPage(page!) 40 + if page != nil {
  41 + ctx?.drawPDFPage(page!)
  42 + }
41 } 43 }
42 44
43 func cancelJob() { 45 func cancelJob() {
@@ -79,4 +81,22 @@ class PdfPrintPageRenderer: UIPrintPageRenderer { @@ -79,4 +81,22 @@ class PdfPrintPageRenderer: UIPrintPageRenderer {
79 81
80 return pages 82 return pages
81 } 83 }
  84 +
  85 + var pageArgs: [String: NSNumber] {
  86 + let width = NSNumber(value: Double(paperRect.size.width))
  87 + let height = NSNumber(value: Double(paperRect.size.height))
  88 + let marginLeft = NSNumber(value: Double(printableRect.origin.x))
  89 + let marginTop = NSNumber(value: Double(printableRect.origin.y))
  90 + let marginRight = NSNumber(value: Double(paperRect.size.width - (printableRect.origin.x + printableRect.size.width)))
  91 + let marginBottom = NSNumber(value: Double(paperRect.size.height - (printableRect.origin.y + printableRect.size.height)))
  92 +
  93 + return [
  94 + "width": width,
  95 + "height": height,
  96 + "marginLeft": marginLeft,
  97 + "marginTop": marginTop,
  98 + "marginRight": marginRight,
  99 + "marginBottom": marginBottom,
  100 + ]
  101 + }
82 } 102 }
@@ -35,10 +35,17 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon @@ -35,10 +35,17 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon
35 registrar.addMethodCallDelegate(instance, channel: channel) 35 registrar.addMethodCallDelegate(instance, channel: channel)
36 } 36 }
37 37
38 - public func handle(_ call: FlutterMethodCall, result: FlutterResult) { 38 + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
39 let args = call.arguments! as! [String: Any] 39 let args = call.arguments! as! [String: Any]
40 if call.method == "printPdf" { 40 if call.method == "printPdf" {
41 - printPdf(args["name"] as? String ?? "") 41 + let name = args["name"] as? String ?? ""
  42 + printPdf(name)
  43 + result(NSNumber(value: 1))
  44 + } else if call.method == "directPrintPdf" {
  45 + let name = args["name"] as? String ?? ""
  46 + let printer = args["printer"] as? String
  47 + let object = args["doc"] as? FlutterStandardTypedData
  48 + directPrintPdf(name: name, data: object!.data, withPrinter: printer!)
42 result(NSNumber(value: 1)) 49 result(NSNumber(value: 1))
43 } else if call.method == "writePdf" { 50 } else if call.method == "writePdf" {
44 if let object = args["doc"] as? FlutterStandardTypedData { 51 if let object = args["doc"] as? FlutterStandardTypedData {
@@ -83,6 +90,13 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon @@ -83,6 +90,13 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon
83 andBaseUrl: args["baseUrl"] as? String == nil ? nil : URL(string: (args["baseUrl"] as? String)!) 90 andBaseUrl: args["baseUrl"] as? String == nil ? nil : URL(string: (args["baseUrl"] as? String)!)
84 ) 91 )
85 result(NSNumber(value: 1)) 92 result(NSNumber(value: 1))
  93 + } else if call.method == "pickPrinter" {
  94 + pickPrinter(result, withSourceRect: CGRect(
  95 + x: CGFloat((args["x"] as? NSNumber)?.floatValue ?? 0.0),
  96 + y: CGFloat((args["y"] as? NSNumber)?.floatValue ?? 0.0),
  97 + width: CGFloat((args["w"] as? NSNumber)?.floatValue ?? 0.0),
  98 + height: CGFloat((args["h"] as? NSNumber)?.floatValue ?? 0.0)
  99 + ))
86 } else { 100 } else {
87 result(FlutterMethodNotImplemented) 101 result(FlutterMethodNotImplemented)
88 } 102 }
@@ -102,6 +116,40 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon @@ -102,6 +116,40 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon
102 renderer = nil 116 renderer = nil
103 } 117 }
104 118
  119 + func directPrintPdf(name: String, data: Data, withPrinter printerID: String) {
  120 + let printing = UIPrintInteractionController.isPrintingAvailable
  121 + if !printing {
  122 + let data: NSDictionary = [
  123 + "completed": false,
  124 + "error": "Printing not available",
  125 + ]
  126 + channel?.invokeMethod("onCompleted", arguments: data)
  127 + return
  128 + }
  129 +
  130 + let controller = UIPrintInteractionController.shared
  131 + controller.delegate = self
  132 +
  133 + let printInfo = UIPrintInfo.printInfo()
  134 + printInfo.jobName = name
  135 + printInfo.outputType = .general
  136 + controller.printInfo = printInfo
  137 + controller.printingItem = data
  138 + let printerURL = URL(string: printerID)
  139 +
  140 + if printerURL == nil {
  141 + let data: NSDictionary = [
  142 + "completed": false,
  143 + "error": "Unable to fine printer URL",
  144 + ]
  145 + channel?.invokeMethod("onCompleted", arguments: data)
  146 + return
  147 + }
  148 +
  149 + let printer = UIPrinter(url: printerURL!)
  150 + controller.print(to: printer, completionHandler: completionHandler)
  151 + }
  152 +
105 func printPdf(_ name: String) { 153 func printPdf(_ name: String) {
106 let printing = UIPrintInteractionController.isPrintingAvailable 154 let printing = UIPrintInteractionController.isPrintingAvailable
107 if !printing { 155 if !printing {
@@ -212,4 +260,41 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon @@ -212,4 +260,41 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon
212 } 260 }
213 }) 261 })
214 } 262 }
  263 +
  264 + func pickPrinter(_ result: @escaping FlutterResult, withSourceRect rect: CGRect) {
  265 + let controller = UIPrinterPickerController(initiallySelectedPrinter: nil)
  266 +
  267 + let pickPrinterCompletionHandler: UIPrinterPickerController.CompletionHandler = {
  268 + (printerPickerController: UIPrinterPickerController, completed: Bool, error: Error?) in
  269 + if !completed, error != nil {
  270 + print("Unable to pick printer: \(error?.localizedDescription ?? "unknown error")")
  271 + result(nil)
  272 + return
  273 + }
  274 +
  275 + if printerPickerController.selectedPrinter == nil {
  276 + result(nil)
  277 + return
  278 + }
  279 +
  280 + let printer = printerPickerController.selectedPrinter!
  281 + let data: NSDictionary = [
  282 + "url": printer.url.absoluteString as Any,
  283 + "name": printer.displayName as Any,
  284 + "model": printer.makeAndModel as Any,
  285 + "location": printer.displayLocation as Any,
  286 + ]
  287 + result(data)
  288 + }
  289 +
  290 + if UI_USER_INTERFACE_IDIOM() == .pad {
  291 + let viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
  292 + if viewController != nil {
  293 + controller.present(from: rect, in: viewController!.view, animated: true, completionHandler: pickPrinterCompletionHandler)
  294 + return
  295 + }
  296 + }
  297 +
  298 + controller.present(animated: true, completionHandler: pickPrinterCompletionHandler)
  299 + }
215 } 300 }
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 library printing; 17 library printing;
18 18
19 import 'dart:async'; 19 import 'dart:async';
  20 +import 'dart:io';
20 import 'dart:typed_data'; 21 import 'dart:typed_data';
21 import 'dart:ui' as ui; 22 import 'dart:ui' as ui;
22 23
@@ -18,6 +18,24 @@ part of printing; @@ -18,6 +18,24 @@ part of printing;
18 18
19 typedef LayoutCallback = FutureOr<List<int>> Function(PdfPageFormat format); 19 typedef LayoutCallback = FutureOr<List<int>> Function(PdfPageFormat format);
20 20
  21 +@immutable
  22 +class Printer {
  23 + const Printer({
  24 + @required this.url,
  25 + this.name,
  26 + this.model,
  27 + this.location,
  28 + }) : assert(url != null);
  29 +
  30 + final String url;
  31 + final String name;
  32 + final String model;
  33 + final String location;
  34 +
  35 + @override
  36 + String toString() => name ?? url;
  37 +}
  38 +
21 mixin Printing { 39 mixin Printing {
22 static const MethodChannel _channel = MethodChannel('printing'); 40 static const MethodChannel _channel = MethodChannel('printing');
23 static LayoutCallback _onLayout; 41 static LayoutCallback _onLayout;
@@ -87,6 +105,61 @@ mixin Printing { @@ -87,6 +105,61 @@ mixin Printing {
87 return _onCompleted.future; 105 return _onCompleted.future;
88 } 106 }
89 107
  108 + /// Opens the native printer picker interface, and returns the URL of the selected printer.
  109 + static Future<Printer> pickPrinter({Rect bounds}) async {
  110 + if (!Platform.isIOS) {
  111 + return null;
  112 + }
  113 + _channel.setMethodCallHandler(_handleMethod);
  114 + bounds ??= Rect.fromCircle(center: Offset.zero, radius: 10);
  115 + final Map<String, dynamic> params = <String, dynamic>{
  116 + 'x': bounds.left,
  117 + 'y': bounds.top,
  118 + 'w': bounds.width,
  119 + 'h': bounds.height,
  120 + };
  121 + final Map<dynamic, dynamic> printer = await _channel
  122 + .invokeMethod<Map<dynamic, dynamic>>('pickPrinter', params);
  123 + print(printer);
  124 + if (printer == null) {
  125 + return null;
  126 + }
  127 + return Printer(
  128 + url: printer['url'],
  129 + name: printer['name'],
  130 + model: printer['model'],
  131 + location: printer['location'],
  132 + );
  133 + }
  134 +
  135 + /// Prints a Pdf document to a specific local printer with no UI
  136 + ///
  137 + /// returns a future with a `bool` set to true if the document is printed
  138 + /// and false if it is canceled.
  139 + /// throws an exception in case of error
  140 + static Future<bool> directPrintPdf({
  141 + @required Printer printer,
  142 + @required LayoutCallback onLayout,
  143 + String name = 'Document',
  144 + }) async {
  145 + if (!Platform.isIOS || printer == null) {
  146 + return false;
  147 + }
  148 + _onCompleted = Completer<bool>();
  149 + _channel.setMethodCallHandler(_handleMethod);
  150 + final List<int> bytes = await onLayout(PdfPageFormat.standard);
  151 + if (bytes == null) {
  152 + return false;
  153 + }
  154 + final Map<String, dynamic> params = <String, dynamic>{
  155 + 'name': name,
  156 + 'printer': printer.url,
  157 + 'doc': Uint8List.fromList(bytes),
  158 + };
  159 + await _channel.invokeMethod<int>('directPrintPdf', params);
  160 + return _onCompleted.future;
  161 + }
  162 +
90 /// Prints a [PdfDocument] or a pdf stream to a local printer using the platform UI 163 /// Prints a [PdfDocument] or a pdf stream to a local printer using the platform UI
91 @Deprecated('use Printing.layoutPdf(onLayout: (_) => document.save());') 164 @Deprecated('use Printing.layoutPdf(onLayout: (_) => document.save());')
92 static Future<void> printPdf({ 165 static Future<void> printPdf({
@@ -4,7 +4,7 @@ description: Plugin that allows Flutter apps to generate and print documents to @@ -4,7 +4,7 @@ description: Plugin that allows Flutter apps to generate and print documents to
4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing 4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing
5 repository: https://github.com/DavBfr/dart_pdf 5 repository: https://github.com/DavBfr/dart_pdf
6 issue_tracker: https://github.com/DavBfr/dart_pdf/issues 6 issue_tracker: https://github.com/DavBfr/dart_pdf/issues
7 -version: 2.1.6 7 +version: 2.1.7
8 8
9 environment: 9 environment:
10 sdk: ">=2.1.0 <3.0.0" 10 sdk: ">=2.1.0 <3.0.0"