Showing
7 changed files
with
236 additions
and
4 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,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 | } |
@@ -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