Showing
7 changed files
with
235 additions
and
3 deletions
| @@ -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,8 +37,10 @@ class PdfPrintPageRenderer: UIPrintPageRenderer { | @@ -37,8 +37,10 @@ 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 | + if page != nil { | ||
| 40 | ctx?.drawPDFPage(page!) | 41 | ctx?.drawPDFPage(page!) |
| 41 | } | 42 | } |
| 43 | + } | ||
| 42 | 44 | ||
| 43 | func cancelJob() { | 45 | func cancelJob() { |
| 44 | pdfDocument = nil | 46 | pdfDocument = nil |
| @@ -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 | } |
| @@ -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" |
-
Please register or login to post a comment