Showing
9 changed files
with
737 additions
and
387 deletions
@@ -14,6 +14,7 @@ | @@ -14,6 +14,7 @@ | ||
14 | - Update android projects (mavenCentral, compileSdkVersion 30, gradle:4.1.0) | 14 | - Update android projects (mavenCentral, compileSdkVersion 30, gradle:4.1.0) |
15 | - Use syscall(SYS_memfd_create) instead of glibc function memfd_create [Obezyan] | 15 | - Use syscall(SYS_memfd_create) instead of glibc function memfd_create [Obezyan] |
16 | - Fix directPrint issue with iOS 15 | 16 | - Fix directPrint issue with iOS 15 |
17 | +- Improve PdfPreview actions | ||
17 | 18 | ||
18 | ## 5.6.6 | 19 | ## 5.6.6 |
19 | 20 |
@@ -22,8 +22,8 @@ export 'src/asset_utils.dart'; | @@ -22,8 +22,8 @@ export 'src/asset_utils.dart'; | ||
22 | export 'src/cache.dart'; | 22 | export 'src/cache.dart'; |
23 | export 'src/callback.dart'; | 23 | export 'src/callback.dart'; |
24 | export 'src/fonts/gfonts.dart'; | 24 | export 'src/fonts/gfonts.dart'; |
25 | +export 'src/preview/actions.dart'; | ||
25 | export 'src/preview/pdf_preview.dart'; | 26 | export 'src/preview/pdf_preview.dart'; |
26 | -export 'src/preview/pdf_preview_action.dart'; | ||
27 | export 'src/printer.dart'; | 27 | export 'src/printer.dart'; |
28 | export 'src/printing.dart'; | 28 | export 'src/printing.dart'; |
29 | export 'src/printing_info.dart'; | 29 | export 'src/printing_info.dart'; |
printing/lib/src/preview/actions.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'dart:math' as math; | ||
18 | + | ||
19 | +import 'package:flutter/foundation.dart'; | ||
20 | +import 'package:flutter/material.dart'; | ||
21 | +import 'package:pdf/pdf.dart'; | ||
22 | + | ||
23 | +import '../../printing.dart'; | ||
24 | +import 'controller.dart'; | ||
25 | + | ||
26 | +/// Base Action callback | ||
27 | +typedef OnPdfPreviewActionPressed = void Function( | ||
28 | + BuildContext context, | ||
29 | + LayoutCallback build, | ||
30 | + PdfPageFormat pageFormat, | ||
31 | +); | ||
32 | + | ||
33 | +mixin PdfPreviewActionBounds { | ||
34 | + final childKey = GlobalKey(); | ||
35 | + | ||
36 | + /// Calculate the widget bounds for iPad popup position | ||
37 | + Rect get bounds { | ||
38 | + final referenceBox = | ||
39 | + childKey.currentContext!.findRenderObject() as RenderBox; | ||
40 | + final topLeft = | ||
41 | + referenceBox.localToGlobal(referenceBox.paintBounds.topLeft); | ||
42 | + final bottomRight = | ||
43 | + referenceBox.localToGlobal(referenceBox.paintBounds.bottomRight); | ||
44 | + return Rect.fromPoints(topLeft, bottomRight); | ||
45 | + } | ||
46 | +} | ||
47 | + | ||
48 | +/// Action to add the the [PdfPreview] widget | ||
49 | +class PdfPreviewAction extends StatelessWidget { | ||
50 | + /// Represents an icon to add to [PdfPreview] | ||
51 | + const PdfPreviewAction({ | ||
52 | + Key? key, | ||
53 | + required this.icon, | ||
54 | + required this.onPressed, | ||
55 | + }) : super(key: key); | ||
56 | + | ||
57 | + /// The icon to display | ||
58 | + final Widget icon; | ||
59 | + | ||
60 | + /// The callback called when the user tap on the icon | ||
61 | + final OnPdfPreviewActionPressed? onPressed; | ||
62 | + | ||
63 | + @override | ||
64 | + Widget build(BuildContext context) { | ||
65 | + return IconButton( | ||
66 | + icon: icon, | ||
67 | + onPressed: onPressed == null ? null : () => pressed(context), | ||
68 | + ); | ||
69 | + } | ||
70 | + | ||
71 | + Future<void> pressed(BuildContext context) async { | ||
72 | + final data = PdfPreviewController.of(context); | ||
73 | + onPressed!(context, data.buildDocument, data.pageFormat); | ||
74 | + } | ||
75 | +} | ||
76 | + | ||
77 | +class PdfPrintAction extends StatelessWidget { | ||
78 | + const PdfPrintAction({ | ||
79 | + Key? key, | ||
80 | + Widget? icon, | ||
81 | + String? jobName, | ||
82 | + this.onPrinted, | ||
83 | + this.onPrintError, | ||
84 | + this.dynamicLayout = true, | ||
85 | + this.usePrinterSettings = false, | ||
86 | + }) : icon = icon ?? const Icon(Icons.print), | ||
87 | + jobName = jobName ?? 'Document', | ||
88 | + super(key: key); | ||
89 | + | ||
90 | + final Widget icon; | ||
91 | + | ||
92 | + final String jobName; | ||
93 | + | ||
94 | + /// Request page re-layout to match the printer paper and margins. | ||
95 | + /// Mitigate an issue with iOS and macOS print dialog that prevent any | ||
96 | + /// channel message while opened. | ||
97 | + final bool dynamicLayout; | ||
98 | + | ||
99 | + /// Set [usePrinterSettings] to true to use the configuration defined by | ||
100 | + /// the printer. May not work for all the printers and can depend on the | ||
101 | + /// drivers. (Supported platforms: Windows) | ||
102 | + final bool usePrinterSettings; | ||
103 | + | ||
104 | + /// Called if the user prints the pdf document | ||
105 | + final VoidCallback? onPrinted; | ||
106 | + | ||
107 | + /// Called if an error creating the Pdf occured | ||
108 | + final void Function(dynamic error)? onPrintError; | ||
109 | + | ||
110 | + @override | ||
111 | + Widget build(BuildContext context) { | ||
112 | + return PdfPreviewAction( | ||
113 | + icon: icon, | ||
114 | + onPressed: _print, | ||
115 | + ); | ||
116 | + } | ||
117 | + | ||
118 | + Future<void> _print( | ||
119 | + BuildContext context, | ||
120 | + LayoutCallback build, | ||
121 | + PdfPageFormat pageFormat, | ||
122 | + ) async { | ||
123 | + final data = PdfPreviewController.of(context); | ||
124 | + | ||
125 | + try { | ||
126 | + final result = await Printing.layoutPdf( | ||
127 | + onLayout: build, | ||
128 | + name: jobName, | ||
129 | + format: data.actualPageFormat, | ||
130 | + dynamicLayout: dynamicLayout, | ||
131 | + usePrinterSettings: usePrinterSettings, | ||
132 | + ); | ||
133 | + | ||
134 | + if (result) { | ||
135 | + onPrinted?.call(); | ||
136 | + } | ||
137 | + } catch (exception, stack) { | ||
138 | + InformationCollector? collector; | ||
139 | + | ||
140 | + assert(() { | ||
141 | + collector = () sync* { | ||
142 | + yield StringProperty('PageFormat', data.actualPageFormat.toString()); | ||
143 | + }; | ||
144 | + return true; | ||
145 | + }()); | ||
146 | + | ||
147 | + FlutterError.reportError(FlutterErrorDetails( | ||
148 | + exception: exception, | ||
149 | + stack: stack, | ||
150 | + library: 'printing', | ||
151 | + context: ErrorDescription('while printing a PDF'), | ||
152 | + informationCollector: collector, | ||
153 | + )); | ||
154 | + | ||
155 | + onPrintError?.call(exception); | ||
156 | + } | ||
157 | + } | ||
158 | +} | ||
159 | + | ||
160 | +class PdfShareAction extends StatelessWidget with PdfPreviewActionBounds { | ||
161 | + PdfShareAction({ | ||
162 | + Key? key, | ||
163 | + Widget? icon, | ||
164 | + String? filename, | ||
165 | + this.subject, | ||
166 | + this.body, | ||
167 | + this.emails, | ||
168 | + this.onShared, | ||
169 | + this.onShareError, | ||
170 | + }) : icon = icon ?? const Icon(Icons.share), | ||
171 | + filename = filename ?? 'document.pdf', | ||
172 | + super(key: key); | ||
173 | + | ||
174 | + final Widget icon; | ||
175 | + | ||
176 | + final String filename; | ||
177 | + | ||
178 | + /// email subject when email application is selected from the share dialog | ||
179 | + final String? subject; | ||
180 | + | ||
181 | + /// extra text to share with Pdf document | ||
182 | + final String? body; | ||
183 | + | ||
184 | + /// list of email addresses which will be filled automatically if the email application | ||
185 | + /// is selected from the share dialog. | ||
186 | + /// This will work only for Android platform. | ||
187 | + final List<String>? emails; | ||
188 | + | ||
189 | + /// Called if the user prints the pdf document | ||
190 | + final VoidCallback? onShared; | ||
191 | + | ||
192 | + /// Called if an error creating the Pdf occured | ||
193 | + final void Function(dynamic error)? onShareError; | ||
194 | + | ||
195 | + @override | ||
196 | + Widget build(BuildContext context) { | ||
197 | + return PdfPreviewAction( | ||
198 | + key: childKey, | ||
199 | + icon: icon, | ||
200 | + onPressed: _share, | ||
201 | + ); | ||
202 | + } | ||
203 | + | ||
204 | + Future<void> _share( | ||
205 | + BuildContext context, | ||
206 | + LayoutCallback build, | ||
207 | + PdfPageFormat pageFormat, | ||
208 | + ) async { | ||
209 | + final bytes = await build(pageFormat); | ||
210 | + | ||
211 | + final result = await Printing.sharePdf( | ||
212 | + bytes: bytes, | ||
213 | + bounds: bounds, | ||
214 | + filename: filename, | ||
215 | + body: body, | ||
216 | + subject: subject, | ||
217 | + emails: emails, | ||
218 | + ); | ||
219 | + | ||
220 | + if (result) { | ||
221 | + onShared?.call(); | ||
222 | + } | ||
223 | + } | ||
224 | +} | ||
225 | + | ||
226 | +class PdfPageFormatAction extends StatelessWidget { | ||
227 | + const PdfPageFormatAction({ | ||
228 | + Key? key, | ||
229 | + required this.pageFormats, | ||
230 | + }) : super(key: key); | ||
231 | + | ||
232 | + /// List of page formats the user can choose | ||
233 | + final Map<String, PdfPageFormat> pageFormats; | ||
234 | + | ||
235 | + @override | ||
236 | + Widget build(BuildContext context) { | ||
237 | + final theme = Theme.of(context); | ||
238 | + final iconColor = theme.primaryIconTheme.color ?? Colors.white; | ||
239 | + final data = PdfPreviewController.listen(context); | ||
240 | + final keys = pageFormats.keys.toList(); | ||
241 | + | ||
242 | + return DropdownButton<PdfPageFormat>( | ||
243 | + dropdownColor: theme.primaryColor, | ||
244 | + icon: Icon( | ||
245 | + Icons.arrow_drop_down, | ||
246 | + color: iconColor, | ||
247 | + ), | ||
248 | + value: data.pageFormat, | ||
249 | + items: List<DropdownMenuItem<PdfPageFormat>>.generate( | ||
250 | + pageFormats.length, | ||
251 | + (int index) { | ||
252 | + final key = keys[index]; | ||
253 | + final val = pageFormats[key]; | ||
254 | + return DropdownMenuItem<PdfPageFormat>( | ||
255 | + value: val, | ||
256 | + child: Text(key, style: TextStyle(color: iconColor)), | ||
257 | + ); | ||
258 | + }, | ||
259 | + ), | ||
260 | + onChanged: (PdfPageFormat? pageFormat) { | ||
261 | + if (pageFormat != null) { | ||
262 | + data.pageFormat = pageFormat; | ||
263 | + } | ||
264 | + }, | ||
265 | + ); | ||
266 | + } | ||
267 | +} | ||
268 | + | ||
269 | +class PdfPageOrientationAction extends StatelessWidget { | ||
270 | + const PdfPageOrientationAction({ | ||
271 | + Key? key, | ||
272 | + }) : super(key: key); | ||
273 | + | ||
274 | + @override | ||
275 | + Widget build(BuildContext context) { | ||
276 | + final theme = Theme.of(context); | ||
277 | + final iconColor = theme.primaryIconTheme.color ?? Colors.white; | ||
278 | + final data = PdfPreviewController.listen(context); | ||
279 | + final horizontal = data.horizontal; | ||
280 | + | ||
281 | + final disabledColor = iconColor.withAlpha(120); | ||
282 | + return ToggleButtons( | ||
283 | + renderBorder: false, | ||
284 | + borderColor: disabledColor, | ||
285 | + color: disabledColor, | ||
286 | + selectedBorderColor: iconColor, | ||
287 | + selectedColor: iconColor, | ||
288 | + onPressed: (int index) { | ||
289 | + data.horizontal = index == 1; | ||
290 | + }, | ||
291 | + isSelected: <bool>[horizontal == false, horizontal == true], | ||
292 | + children: <Widget>[ | ||
293 | + Transform.rotate( | ||
294 | + angle: -math.pi / 2, | ||
295 | + child: const Icon( | ||
296 | + Icons.note_outlined, | ||
297 | + ), | ||
298 | + ), | ||
299 | + const Icon(Icons.note_outlined), | ||
300 | + ], | ||
301 | + ); | ||
302 | + } | ||
303 | +} |
printing/lib/src/preview/controller.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'package:flutter/material.dart'; | ||
18 | +import 'package:pdf/pdf.dart'; | ||
19 | + | ||
20 | +import '../callback.dart'; | ||
21 | + | ||
22 | +mixin PdfPreviewData implements ChangeNotifier { | ||
23 | + // PdfPreviewData(this.build); | ||
24 | + | ||
25 | + LayoutCallback get buildDocument; | ||
26 | + | ||
27 | + PdfPageFormat? _pageFormat; | ||
28 | + | ||
29 | + PdfPageFormat get initialPageFormat; | ||
30 | + | ||
31 | + PdfPageFormat get pageFormat => _pageFormat ?? initialPageFormat; | ||
32 | + | ||
33 | + set pageFormat(PdfPageFormat value) { | ||
34 | + if (_pageFormat == value) { | ||
35 | + return; | ||
36 | + } | ||
37 | + _pageFormat = value; | ||
38 | + notifyListeners(); | ||
39 | + } | ||
40 | + | ||
41 | + bool? _horizontal; | ||
42 | + | ||
43 | + /// Is the print horizontal | ||
44 | + bool get horizontal => _horizontal ?? pageFormat.width > pageFormat.height; | ||
45 | + | ||
46 | + set horizontal(bool value) { | ||
47 | + if (_horizontal == value) { | ||
48 | + return; | ||
49 | + } | ||
50 | + _horizontal = value; | ||
51 | + notifyListeners(); | ||
52 | + } | ||
53 | + | ||
54 | + /// Computed page format | ||
55 | + PdfPageFormat get computedPageFormat => | ||
56 | + horizontal ? pageFormat.landscape : pageFormat.portrait; | ||
57 | + | ||
58 | + String get localPageFormat { | ||
59 | + final locale = WidgetsBinding.instance!.window.locale; | ||
60 | + // ignore: unnecessary_cast | ||
61 | + final cc = (locale as Locale?)?.countryCode ?? 'US'; | ||
62 | + | ||
63 | + if (cc == 'US' || cc == 'CA' || cc == 'MX') { | ||
64 | + return 'Letter'; | ||
65 | + } | ||
66 | + return 'A4'; | ||
67 | + } | ||
68 | + | ||
69 | + PdfPageFormat get actualPageFormat => pageFormat; | ||
70 | +} | ||
71 | + | ||
72 | +class PdfPreviewController extends InheritedNotifier { | ||
73 | + const PdfPreviewController({ | ||
74 | + Key? key, | ||
75 | + required this.data, | ||
76 | + required Widget child, | ||
77 | + }) : super(key: key, child: child, notifier: data); | ||
78 | + | ||
79 | + final PdfPreviewData data; | ||
80 | + | ||
81 | + static PdfPreviewData of(BuildContext context) { | ||
82 | + final result = | ||
83 | + context.findAncestorWidgetOfExactType<PdfPreviewController>(); | ||
84 | + assert(result != null, 'No PdfPreview found in context'); | ||
85 | + return result!.data; | ||
86 | + } | ||
87 | + | ||
88 | + static PdfPreviewData listen(BuildContext context) { | ||
89 | + final result = | ||
90 | + context.dependOnInheritedWidgetOfExactType<PdfPreviewController>(); | ||
91 | + assert(result != null, 'No PdfPreview found in context'); | ||
92 | + return result!.data; | ||
93 | + } | ||
94 | + | ||
95 | + @override | ||
96 | + bool updateShouldNotify(covariant InheritedWidget oldWidget) { | ||
97 | + return false; | ||
98 | + } | ||
99 | +} |
printing/lib/src/preview/custom.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import 'dart:async'; | ||
18 | + | ||
19 | +import 'package:flutter/material.dart'; | ||
20 | +import 'package:pdf/pdf.dart'; | ||
21 | + | ||
22 | +import '../callback.dart'; | ||
23 | +import '../printing.dart'; | ||
24 | +import '../printing_info.dart'; | ||
25 | +import 'raster.dart'; | ||
26 | + | ||
27 | +/// Flutter widget that uses the rasterized pdf pages to display a document. | ||
28 | +class PdfPreviewCustom extends StatefulWidget { | ||
29 | + /// Show a pdf document built on demand | ||
30 | + const PdfPreviewCustom({ | ||
31 | + Key? key, | ||
32 | + this.pageFormat = PdfPageFormat.a4, | ||
33 | + required this.build, | ||
34 | + this.maxPageWidth, | ||
35 | + this.onError, | ||
36 | + this.scrollViewDecoration, | ||
37 | + this.pdfPreviewPageDecoration, | ||
38 | + this.pages, | ||
39 | + this.previewPageMargin, | ||
40 | + this.padding, | ||
41 | + this.shouldRepaint = false, | ||
42 | + this.loadingWidget, | ||
43 | + }) : super(key: key); | ||
44 | + | ||
45 | + /// Pdf paper page format | ||
46 | + final PdfPageFormat pageFormat; | ||
47 | + | ||
48 | + /// Called when a pdf document is needed | ||
49 | + final LayoutCallback build; | ||
50 | + | ||
51 | + /// Maximum width of the pdf document on screen | ||
52 | + final double? maxPageWidth; | ||
53 | + | ||
54 | + /// Widget to display if the PDF document cannot be displayed | ||
55 | + final Widget Function(BuildContext context, Object error)? onError; | ||
56 | + | ||
57 | + /// Decoration of scrollView | ||
58 | + final Decoration? scrollViewDecoration; | ||
59 | + | ||
60 | + /// Decoration of PdfPreviewPage | ||
61 | + final Decoration? pdfPreviewPageDecoration; | ||
62 | + | ||
63 | + /// Pages to display. Default will display all the pages. | ||
64 | + final List<int>? pages; | ||
65 | + | ||
66 | + /// margin for the document preview page | ||
67 | + /// | ||
68 | + /// defaults to [EdgeInsets.only(left: 20, top: 8, right: 20, bottom: 12)], | ||
69 | + final EdgeInsets? previewPageMargin; | ||
70 | + | ||
71 | + /// padding for the pdf_preview widget | ||
72 | + final EdgeInsets? padding; | ||
73 | + | ||
74 | + /// Force repainting the PDF document | ||
75 | + final bool shouldRepaint; | ||
76 | + | ||
77 | + /// Custom loading widget to use that is shown while PDF is being generated. | ||
78 | + /// If null, a [CircularProgressIndicator] is used instead. | ||
79 | + final Widget? loadingWidget; | ||
80 | + | ||
81 | + @override | ||
82 | + PdfPreviewCustomState createState() => PdfPreviewCustomState(); | ||
83 | +} | ||
84 | + | ||
85 | +class PdfPreviewCustomState extends State<PdfPreviewCustom> | ||
86 | + with PdfPreviewRaster { | ||
87 | + final listView = GlobalKey(); | ||
88 | + | ||
89 | + bool infoLoaded = false; | ||
90 | + | ||
91 | + int? preview; | ||
92 | + | ||
93 | + double? updatePosition; | ||
94 | + | ||
95 | + final scrollController = ScrollController( | ||
96 | + keepScrollOffset: true, | ||
97 | + ); | ||
98 | + | ||
99 | + final transformationController = TransformationController(); | ||
100 | + | ||
101 | + Timer? previewUpdate; | ||
102 | + | ||
103 | + static const _errorMessage = 'Unable to display the document'; | ||
104 | + | ||
105 | + @override | ||
106 | + void dispose() { | ||
107 | + previewUpdate?.cancel(); | ||
108 | + super.dispose(); | ||
109 | + } | ||
110 | + | ||
111 | + @override | ||
112 | + void reassemble() { | ||
113 | + raster(); | ||
114 | + super.reassemble(); | ||
115 | + } | ||
116 | + | ||
117 | + @override | ||
118 | + void didUpdateWidget(covariant PdfPreviewCustom oldWidget) { | ||
119 | + if (oldWidget.build != widget.build || | ||
120 | + widget.shouldRepaint || | ||
121 | + widget.pageFormat != oldWidget.pageFormat) { | ||
122 | + preview = null; | ||
123 | + updatePosition = null; | ||
124 | + raster(); | ||
125 | + } | ||
126 | + super.didUpdateWidget(oldWidget); | ||
127 | + } | ||
128 | + | ||
129 | + @override | ||
130 | + void didChangeDependencies() { | ||
131 | + if (!infoLoaded) { | ||
132 | + infoLoaded = true; | ||
133 | + Printing.info().then((PrintingInfo _info) { | ||
134 | + setState(() { | ||
135 | + info = _info; | ||
136 | + raster(); | ||
137 | + }); | ||
138 | + }); | ||
139 | + } | ||
140 | + | ||
141 | + raster(); | ||
142 | + super.didChangeDependencies(); | ||
143 | + } | ||
144 | + | ||
145 | + Widget _showError(Object error) { | ||
146 | + if (widget.onError != null) { | ||
147 | + return widget.onError!(context, error); | ||
148 | + } | ||
149 | + | ||
150 | + return ErrorWidget(error); | ||
151 | + } | ||
152 | + | ||
153 | + Widget _createPreview() { | ||
154 | + if (error != null) { | ||
155 | + return _showError(error!); | ||
156 | + } | ||
157 | + | ||
158 | + final _info = info; | ||
159 | + if (_info != null && !_info.canRaster) { | ||
160 | + return _showError(_errorMessage); | ||
161 | + } | ||
162 | + | ||
163 | + if (pages.isEmpty) { | ||
164 | + return widget.loadingWidget ?? | ||
165 | + const Center( | ||
166 | + child: CircularProgressIndicator(), | ||
167 | + ); | ||
168 | + } | ||
169 | + | ||
170 | + return ListView.builder( | ||
171 | + controller: scrollController, | ||
172 | + padding: widget.padding, | ||
173 | + itemCount: pages.length, | ||
174 | + itemBuilder: (BuildContext context, int index) => GestureDetector( | ||
175 | + onDoubleTap: () { | ||
176 | + setState(() { | ||
177 | + updatePosition = scrollController.position.pixels; | ||
178 | + preview = index; | ||
179 | + transformationController.value.setIdentity(); | ||
180 | + }); | ||
181 | + }, | ||
182 | + child: pages[index], | ||
183 | + ), | ||
184 | + ); | ||
185 | + } | ||
186 | + | ||
187 | + Widget _zoomPreview() { | ||
188 | + return GestureDetector( | ||
189 | + onDoubleTap: () { | ||
190 | + setState(() { | ||
191 | + preview = null; | ||
192 | + }); | ||
193 | + }, | ||
194 | + child: InteractiveViewer( | ||
195 | + transformationController: transformationController, | ||
196 | + maxScale: 5, | ||
197 | + child: Center(child: pages[preview!]), | ||
198 | + ), | ||
199 | + ); | ||
200 | + } | ||
201 | + | ||
202 | + @override | ||
203 | + Widget build(BuildContext context) { | ||
204 | + Widget page; | ||
205 | + | ||
206 | + if (preview != null) { | ||
207 | + page = _zoomPreview(); | ||
208 | + } else { | ||
209 | + page = Container( | ||
210 | + constraints: widget.maxPageWidth != null | ||
211 | + ? BoxConstraints(maxWidth: widget.maxPageWidth!) | ||
212 | + : null, | ||
213 | + child: _createPreview(), | ||
214 | + ); | ||
215 | + | ||
216 | + if (updatePosition != null) { | ||
217 | + Timer.run(() { | ||
218 | + scrollController.jumpTo(updatePosition!); | ||
219 | + updatePosition = null; | ||
220 | + }); | ||
221 | + } | ||
222 | + } | ||
223 | + | ||
224 | + return Container( | ||
225 | + decoration: widget.scrollViewDecoration ?? | ||
226 | + BoxDecoration( | ||
227 | + gradient: LinearGradient( | ||
228 | + colors: <Color>[Colors.grey.shade400, Colors.grey.shade200], | ||
229 | + begin: Alignment.topCenter, | ||
230 | + end: Alignment.bottomCenter, | ||
231 | + ), | ||
232 | + ), | ||
233 | + width: double.infinity, | ||
234 | + alignment: Alignment.center, | ||
235 | + child: page, | ||
236 | + ); | ||
237 | + } | ||
238 | +} |
@@ -14,10 +14,6 @@ | @@ -14,10 +14,6 @@ | ||
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | 16 | ||
17 | -import 'dart:async'; | ||
18 | -import 'dart:math'; | ||
19 | - | ||
20 | -import 'package:flutter/foundation.dart'; | ||
21 | import 'package:flutter/material.dart'; | 17 | import 'package:flutter/material.dart'; |
22 | import 'package:pdf/pdf.dart'; | 18 | import 'package:pdf/pdf.dart'; |
23 | import 'package:pdf/widgets.dart' as pw; | 19 | import 'package:pdf/widgets.dart' as pw; |
@@ -25,8 +21,11 @@ import 'package:pdf/widgets.dart' as pw; | @@ -25,8 +21,11 @@ import 'package:pdf/widgets.dart' as pw; | ||
25 | import '../callback.dart'; | 21 | import '../callback.dart'; |
26 | import '../printing.dart'; | 22 | import '../printing.dart'; |
27 | import '../printing_info.dart'; | 23 | import '../printing_info.dart'; |
28 | -import 'pdf_preview_action.dart'; | ||
29 | -import 'pdf_preview_raster.dart'; | 24 | +import 'actions.dart'; |
25 | +import 'controller.dart'; | ||
26 | +import 'custom.dart'; | ||
27 | + | ||
28 | +export 'custom.dart'; | ||
30 | 29 | ||
31 | /// Flutter widget that uses the rasterized pdf pages to display a document. | 30 | /// Flutter widget that uses the rasterized pdf pages to display a document. |
32 | class PdfPreview extends StatefulWidget { | 31 | class PdfPreview extends StatefulWidget { |
@@ -115,7 +114,7 @@ class PdfPreview extends StatefulWidget { | @@ -115,7 +114,7 @@ class PdfPreview extends StatefulWidget { | ||
115 | /// Decoration of scrollView | 114 | /// Decoration of scrollView |
116 | final Decoration? scrollViewDecoration; | 115 | final Decoration? scrollViewDecoration; |
117 | 116 | ||
118 | - /// Decoration of _PdfPreviewPage | 117 | + /// Decoration of PdfPreviewPage |
119 | final Decoration? pdfPreviewPageDecoration; | 118 | final Decoration? pdfPreviewPageDecoration; |
120 | 119 | ||
121 | /// Name of the PDF when sharing. It must include the extension. | 120 | /// Name of the PDF when sharing. It must include the extension. |
@@ -159,89 +158,64 @@ class PdfPreview extends StatefulWidget { | @@ -159,89 +158,64 @@ class PdfPreview extends StatefulWidget { | ||
159 | _PdfPreviewState createState() => _PdfPreviewState(); | 158 | _PdfPreviewState createState() => _PdfPreviewState(); |
160 | } | 159 | } |
161 | 160 | ||
162 | -class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | ||
163 | - final GlobalKey<State<StatefulWidget>> shareWidget = GlobalKey(); | ||
164 | - final GlobalKey<State<StatefulWidget>> listView = GlobalKey(); | 161 | +class _PdfPreviewState extends State<PdfPreview> |
162 | + with PdfPreviewData, ChangeNotifier { | ||
163 | + final previewWidget = GlobalKey<PdfPreviewCustomState>(); | ||
165 | 164 | ||
166 | - PdfPageFormat? _pageFormat; | 165 | + /// Printing subsystem information |
166 | + PrintingInfo? info; | ||
167 | + var infoLoaded = false; | ||
167 | 168 | ||
168 | - String get localPageFormat { | ||
169 | - final locale = WidgetsBinding.instance!.window.locale; | ||
170 | - // ignore: unnecessary_cast | ||
171 | - final cc = (locale as Locale?)?.countryCode ?? 'US'; | 169 | + @override |
170 | + LayoutCallback get buildDocument => widget.build; | ||
172 | 171 | ||
173 | - if (cc == 'US' || cc == 'CA' || cc == 'MX') { | ||
174 | - return 'Letter'; | ||
175 | - } | ||
176 | - return 'A4'; | ||
177 | - } | 172 | + @override |
173 | + PdfPageFormat get initialPageFormat => | ||
174 | + widget.initialPageFormat ?? | ||
175 | + (widget.pageFormats.isNotEmpty | ||
176 | + ? (widget.pageFormats[localPageFormat] ?? | ||
177 | + widget.pageFormats.values.first) | ||
178 | + : (PdfPreview._defaultPageFormats[localPageFormat]) ?? | ||
179 | + PdfPreview._defaultPageFormats.values.first); | ||
178 | 180 | ||
179 | @override | 181 | @override |
180 | PdfPageFormat get pageFormat { | 182 | PdfPageFormat get pageFormat { |
181 | - _pageFormat ??= widget.initialPageFormat == null | ||
182 | - ? widget.pageFormats[localPageFormat] | ||
183 | - : _pageFormat = widget.initialPageFormat!; | 183 | + var _pageFormat = super.pageFormat; |
184 | 184 | ||
185 | if (!widget.pageFormats.containsValue(_pageFormat)) { | 185 | if (!widget.pageFormats.containsValue(_pageFormat)) { |
186 | - _pageFormat = widget.initialPageFormat ?? | ||
187 | - (widget.pageFormats.isNotEmpty | ||
188 | - ? widget.pageFormats.values.first | ||
189 | - : PdfPreview._defaultPageFormats[localPageFormat]); | 186 | + _pageFormat = initialPageFormat; |
187 | + pageFormat = _pageFormat; | ||
190 | } | 188 | } |
191 | 189 | ||
192 | - return _pageFormat!; | 190 | + return _pageFormat; |
193 | } | 191 | } |
194 | 192 | ||
195 | - bool infoLoaded = false; | ||
196 | - | ||
197 | - int? preview; | ||
198 | - | ||
199 | - double? updatePosition; | ||
200 | - | ||
201 | - final scrollController = ScrollController( | ||
202 | - keepScrollOffset: true, | ||
203 | - ); | ||
204 | - | ||
205 | - final transformationController = TransformationController(); | ||
206 | - | ||
207 | - Timer? previewUpdate; | ||
208 | - | ||
209 | - static const _errorMessage = 'Unable to display the document'; | ||
210 | - | ||
211 | @override | 193 | @override |
212 | - void initState() { | ||
213 | - if (widget.initialPageFormat == null) { | ||
214 | - final locale = WidgetsBinding.instance!.window.locale; | ||
215 | - // ignore: unnecessary_cast | ||
216 | - final cc = (locale as Locale?)?.countryCode ?? 'US'; | ||
217 | - | ||
218 | - if (cc == 'US' || cc == 'CA' || cc == 'MX') { | ||
219 | - _pageFormat = PdfPageFormat.letter; | ||
220 | - } else { | ||
221 | - _pageFormat = PdfPageFormat.a4; | ||
222 | - } | ||
223 | - } else { | ||
224 | - _pageFormat = widget.initialPageFormat!; | ||
225 | - } | 194 | + PdfPageFormat get actualPageFormat { |
195 | + var format = pageFormat; | ||
196 | + final pages = previewWidget.currentState?.pages ?? const []; | ||
197 | + final dpi = previewWidget.currentState?.dpi ?? 72; | ||
226 | 198 | ||
227 | - final _pageFormats = widget.pageFormats; | ||
228 | - if (!_pageFormats.containsValue(pageFormat)) { | ||
229 | - _pageFormat = _pageFormats.values.first; | 199 | + if (!widget.canChangePageFormat && pages.isNotEmpty) { |
200 | + format = PdfPageFormat( | ||
201 | + pages.first.page!.width * 72 / dpi, | ||
202 | + pages.first.page!.height * 72 / dpi, | ||
203 | + marginAll: 5 * PdfPageFormat.mm, | ||
204 | + ); | ||
230 | } | 205 | } |
231 | 206 | ||
232 | - super.initState(); | 207 | + return format; |
233 | } | 208 | } |
234 | 209 | ||
235 | @override | 210 | @override |
236 | - void dispose() { | ||
237 | - previewUpdate?.cancel(); | ||
238 | - super.dispose(); | 211 | + void initState() { |
212 | + addListener(() { | ||
213 | + if (mounted) { | ||
214 | + setState(() {}); | ||
239 | } | 215 | } |
216 | + }); | ||
240 | 217 | ||
241 | - @override | ||
242 | - void reassemble() { | ||
243 | - raster(); | ||
244 | - super.reassemble(); | 218 | + super.initState(); |
245 | } | 219 | } |
246 | 220 | ||
247 | @override | 221 | @override |
@@ -249,10 +223,7 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | @@ -249,10 +223,7 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | ||
249 | if (oldWidget.build != widget.build || | 223 | if (oldWidget.build != widget.build || |
250 | widget.shouldRepaint || | 224 | widget.shouldRepaint || |
251 | widget.pageFormats != oldWidget.pageFormats) { | 225 | widget.pageFormats != oldWidget.pageFormats) { |
252 | - preview = null; | ||
253 | - updatePosition = null; | ||
254 | - | ||
255 | - raster(); | 226 | + setState(() {}); |
256 | } | 227 | } |
257 | super.didUpdateWidget(oldWidget); | 228 | super.didUpdateWidget(oldWidget); |
258 | } | 229 | } |
@@ -264,208 +235,51 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | @@ -264,208 +235,51 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | ||
264 | Printing.info().then((PrintingInfo _info) { | 235 | Printing.info().then((PrintingInfo _info) { |
265 | setState(() { | 236 | setState(() { |
266 | info = _info; | 237 | info = _info; |
267 | - raster(); | ||
268 | }); | 238 | }); |
269 | }); | 239 | }); |
270 | } | 240 | } |
271 | 241 | ||
272 | - raster(); | ||
273 | super.didChangeDependencies(); | 242 | super.didChangeDependencies(); |
274 | } | 243 | } |
275 | 244 | ||
276 | - Widget _showError(Object error) { | ||
277 | - if (widget.onError != null) { | ||
278 | - return widget.onError!(context, error); | ||
279 | - } | ||
280 | - | ||
281 | - return ErrorWidget(error); | ||
282 | - } | ||
283 | - | ||
284 | - Widget _createPreview() { | ||
285 | - if (error != null) { | ||
286 | - return _showError(error!); | ||
287 | - } | ||
288 | - | ||
289 | - final _info = info; | ||
290 | - if (_info != null && !_info.canRaster) { | ||
291 | - return _showError(_errorMessage); | ||
292 | - } | ||
293 | - | ||
294 | - if (pages.isEmpty) { | ||
295 | - return widget.loadingWidget ?? | ||
296 | - const Center( | ||
297 | - child: CircularProgressIndicator(), | ||
298 | - ); | ||
299 | - } | ||
300 | - | ||
301 | - return ListView.builder( | ||
302 | - controller: scrollController, | ||
303 | - padding: widget.padding, | ||
304 | - itemCount: pages.length, | ||
305 | - itemBuilder: (BuildContext context, int index) => GestureDetector( | ||
306 | - onDoubleTap: () { | ||
307 | - setState(() { | ||
308 | - updatePosition = scrollController.position.pixels; | ||
309 | - preview = index; | ||
310 | - transformationController.value.setIdentity(); | ||
311 | - }); | ||
312 | - }, | ||
313 | - child: pages[index], | ||
314 | - ), | ||
315 | - ); | ||
316 | - } | ||
317 | - | ||
318 | - Widget _zoomPreview() { | ||
319 | - return GestureDetector( | ||
320 | - onDoubleTap: () { | ||
321 | - setState(() { | ||
322 | - preview = null; | ||
323 | - }); | ||
324 | - }, | ||
325 | - child: InteractiveViewer( | ||
326 | - transformationController: transformationController, | ||
327 | - maxScale: 5, | ||
328 | - child: Center(child: pages[preview!]), | ||
329 | - ), | ||
330 | - ); | ||
331 | - } | ||
332 | - | ||
333 | @override | 245 | @override |
334 | Widget build(BuildContext context) { | 246 | Widget build(BuildContext context) { |
335 | final theme = Theme.of(context); | 247 | final theme = Theme.of(context); |
336 | final iconColor = theme.primaryIconTheme.color ?? Colors.white; | 248 | final iconColor = theme.primaryIconTheme.color ?? Colors.white; |
337 | 249 | ||
338 | - Widget page; | ||
339 | - | ||
340 | - if (preview != null) { | ||
341 | - page = _zoomPreview(); | ||
342 | - } else { | ||
343 | - page = Container( | ||
344 | - constraints: widget.maxPageWidth != null | ||
345 | - ? BoxConstraints(maxWidth: widget.maxPageWidth!) | ||
346 | - : null, | ||
347 | - child: _createPreview(), | ||
348 | - ); | ||
349 | - | ||
350 | - if (updatePosition != null) { | ||
351 | - Timer.run(() { | ||
352 | - scrollController.jumpTo(updatePosition!); | ||
353 | - updatePosition = null; | ||
354 | - }); | ||
355 | - } | ||
356 | - } | ||
357 | - | ||
358 | - final Widget scrollView = Container( | ||
359 | - decoration: widget.scrollViewDecoration ?? | ||
360 | - BoxDecoration( | ||
361 | - gradient: LinearGradient( | ||
362 | - colors: <Color>[Colors.grey.shade400, Colors.grey.shade200], | ||
363 | - begin: Alignment.topCenter, | ||
364 | - end: Alignment.bottomCenter, | ||
365 | - ), | ||
366 | - ), | ||
367 | - width: double.infinity, | ||
368 | - alignment: Alignment.center, | ||
369 | - child: page, | ||
370 | - ); | ||
371 | - | ||
372 | final actions = <Widget>[]; | 250 | final actions = <Widget>[]; |
373 | 251 | ||
374 | if (widget.allowPrinting && info?.canPrint == true) { | 252 | if (widget.allowPrinting && info?.canPrint == true) { |
375 | - actions.add( | ||
376 | - IconButton( | ||
377 | - icon: const Icon(Icons.print), | ||
378 | - onPressed: _print, | ||
379 | - ), | ||
380 | - ); | 253 | + actions.add(PdfPrintAction( |
254 | + jobName: widget.pdfFileName, | ||
255 | + dynamicLayout: widget.dynamicLayout, | ||
256 | + onPrinted: | ||
257 | + widget.onPrinted == null ? null : () => widget.onPrinted!(context), | ||
258 | + onPrintError: widget.onPrintError == null | ||
259 | + ? null | ||
260 | + : (dynamic error) => widget.onPrintError!(context, error), | ||
261 | + )); | ||
381 | } | 262 | } |
382 | 263 | ||
383 | if (widget.allowSharing && info?.canShare == true) { | 264 | if (widget.allowSharing && info?.canShare == true) { |
384 | - actions.add( | ||
385 | - IconButton( | ||
386 | - key: shareWidget, | ||
387 | - icon: const Icon(Icons.share), | ||
388 | - onPressed: _share, | ||
389 | - ), | ||
390 | - ); | 265 | + actions.add(PdfShareAction( |
266 | + filename: widget.pdfFileName, | ||
267 | + onShared: | ||
268 | + widget.onPrinted == null ? null : () => widget.onPrinted!(context), | ||
269 | + )); | ||
391 | } | 270 | } |
392 | 271 | ||
393 | if (widget.canChangePageFormat) { | 272 | if (widget.canChangePageFormat) { |
394 | - final keys = widget.pageFormats.keys.toList(); | ||
395 | - actions.add( | ||
396 | - DropdownButton<PdfPageFormat>( | ||
397 | - dropdownColor: theme.primaryColor, | ||
398 | - icon: Icon( | ||
399 | - Icons.arrow_drop_down, | ||
400 | - color: iconColor, | ||
401 | - ), | ||
402 | - value: pageFormat, | ||
403 | - items: List<DropdownMenuItem<PdfPageFormat>>.generate( | ||
404 | - widget.pageFormats.length, | ||
405 | - (int index) { | ||
406 | - final key = keys[index]; | ||
407 | - final val = widget.pageFormats[key]; | ||
408 | - return DropdownMenuItem<PdfPageFormat>( | ||
409 | - value: val, | ||
410 | - child: Text(key, style: TextStyle(color: iconColor)), | ||
411 | - ); | ||
412 | - }, | ||
413 | - ), | ||
414 | - onChanged: (PdfPageFormat? pageFormat) { | ||
415 | - setState(() { | ||
416 | - if (pageFormat != null) { | ||
417 | - _pageFormat = pageFormat; | ||
418 | - raster(); | ||
419 | - } | ||
420 | - }); | ||
421 | - }, | ||
422 | - ), | ||
423 | - ); | 273 | + actions.add(PdfPageFormatAction( |
274 | + pageFormats: widget.pageFormats, | ||
275 | + )); | ||
424 | 276 | ||
425 | if (widget.canChangeOrientation) { | 277 | if (widget.canChangeOrientation) { |
426 | - horizontal ??= pageFormat.width > pageFormat.height; | ||
427 | - | ||
428 | - final disabledColor = iconColor.withAlpha(120); | ||
429 | - actions.add( | ||
430 | - ToggleButtons( | ||
431 | - renderBorder: false, | ||
432 | - borderColor: disabledColor, | ||
433 | - color: disabledColor, | ||
434 | - selectedBorderColor: iconColor, | ||
435 | - selectedColor: iconColor, | ||
436 | - onPressed: (int index) { | ||
437 | - setState(() { | ||
438 | - horizontal = index == 1; | ||
439 | - raster(); | ||
440 | - }); | ||
441 | - }, | ||
442 | - isSelected: <bool>[horizontal == false, horizontal == true], | ||
443 | - children: <Widget>[ | ||
444 | - Transform.rotate( | ||
445 | - angle: -pi / 2, child: const Icon(Icons.note_outlined)), | ||
446 | - const Icon(Icons.note_outlined), | ||
447 | - ], | ||
448 | - ), | ||
449 | - ); | 278 | + actions.add(const PdfPageOrientationAction()); |
450 | } | 279 | } |
451 | } | 280 | } |
452 | 281 | ||
453 | - if (widget.actions != null) { | ||
454 | - for (final action in widget.actions!) { | ||
455 | - actions.add( | ||
456 | - IconButton( | ||
457 | - icon: action.icon, | ||
458 | - onPressed: action.onPressed == null | ||
459 | - ? null | ||
460 | - : () => action.onPressed!( | ||
461 | - context, | ||
462 | - widget.build, | ||
463 | - computedPageFormat, | ||
464 | - ), | ||
465 | - ), | ||
466 | - ); | ||
467 | - } | ||
468 | - } | 282 | + widget.actions?.forEach(actions.add); |
469 | 283 | ||
470 | assert(() { | 284 | assert(() { |
471 | if (actions.isNotEmpty && widget.canDebug) { | 285 | if (actions.isNotEmpty && widget.canDebug) { |
@@ -474,12 +288,10 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | @@ -474,12 +288,10 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | ||
474 | activeColor: Colors.red, | 288 | activeColor: Colors.red, |
475 | value: pw.Document.debug, | 289 | value: pw.Document.debug, |
476 | onChanged: (bool value) { | 290 | onChanged: (bool value) { |
477 | - setState( | ||
478 | - () { | 291 | + setState(() { |
479 | pw.Document.debug = value; | 292 | pw.Document.debug = value; |
480 | - raster(); | ||
481 | - }, | ||
482 | - ); | 293 | + }); |
294 | + previewWidget.currentState?.raster(); | ||
483 | }, | 295 | }, |
484 | ), | 296 | ), |
485 | ); | 297 | ); |
@@ -488,10 +300,27 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | @@ -488,10 +300,27 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | ||
488 | return true; | 300 | return true; |
489 | }()); | 301 | }()); |
490 | 302 | ||
491 | - return Column( | 303 | + return PdfPreviewController( |
304 | + data: this, | ||
305 | + child: Column( | ||
492 | mainAxisAlignment: MainAxisAlignment.center, | 306 | mainAxisAlignment: MainAxisAlignment.center, |
493 | children: <Widget>[ | 307 | children: <Widget>[ |
494 | - Expanded(child: scrollView), | 308 | + Expanded( |
309 | + child: PdfPreviewCustom( | ||
310 | + key: previewWidget, | ||
311 | + build: widget.build, | ||
312 | + loadingWidget: widget.loadingWidget, | ||
313 | + maxPageWidth: widget.maxPageWidth, | ||
314 | + onError: widget.onError, | ||
315 | + padding: widget.padding, | ||
316 | + pageFormat: computedPageFormat, | ||
317 | + pages: widget.pages, | ||
318 | + pdfPreviewPageDecoration: widget.pdfPreviewPageDecoration, | ||
319 | + previewPageMargin: widget.previewPageMargin, | ||
320 | + scrollViewDecoration: widget.scrollViewDecoration, | ||
321 | + shouldRepaint: widget.shouldRepaint, | ||
322 | + ), | ||
323 | + ), | ||
495 | if (actions.isNotEmpty && widget.useActions) | 324 | if (actions.isNotEmpty && widget.useActions) |
496 | IconTheme.merge( | 325 | IconTheme.merge( |
497 | data: IconThemeData( | 326 | data: IconThemeData( |
@@ -510,79 +339,9 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | @@ -510,79 +339,9 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster { | ||
510 | ), | 339 | ), |
511 | ), | 340 | ), |
512 | ), | 341 | ), |
513 | - ) | 342 | + ), |
514 | ], | 343 | ], |
344 | + ), | ||
515 | ); | 345 | ); |
516 | } | 346 | } |
517 | - | ||
518 | - Future<void> _print() async { | ||
519 | - var format = computedPageFormat; | ||
520 | - | ||
521 | - if (!widget.canChangePageFormat && pages.isNotEmpty) { | ||
522 | - format = PdfPageFormat( | ||
523 | - pages.first.page!.width * 72 / dpi, | ||
524 | - pages.first.page!.height * 72 / dpi, | ||
525 | - marginAll: 5 * PdfPageFormat.mm, | ||
526 | - ); | ||
527 | - } | ||
528 | - | ||
529 | - try { | ||
530 | - final result = await Printing.layoutPdf( | ||
531 | - onLayout: widget.build, | ||
532 | - name: widget.pdfFileName ?? 'Document', | ||
533 | - format: format, | ||
534 | - dynamicLayout: widget.dynamicLayout, | ||
535 | - ); | ||
536 | - | ||
537 | - if (result && widget.onPrinted != null) { | ||
538 | - widget.onPrinted!(context); | ||
539 | - } | ||
540 | - } catch (exception, stack) { | ||
541 | - InformationCollector? collector; | ||
542 | - | ||
543 | - assert(() { | ||
544 | - collector = () sync* { | ||
545 | - yield StringProperty('PageFormat', computedPageFormat.toString()); | ||
546 | - }; | ||
547 | - return true; | ||
548 | - }()); | ||
549 | - | ||
550 | - FlutterError.reportError(FlutterErrorDetails( | ||
551 | - exception: exception, | ||
552 | - stack: stack, | ||
553 | - library: 'printing', | ||
554 | - context: ErrorDescription('while printing a PDF'), | ||
555 | - informationCollector: collector, | ||
556 | - )); | ||
557 | - | ||
558 | - if (widget.onPrintError != null) { | ||
559 | - widget.onPrintError!(context, exception); | ||
560 | - } | ||
561 | - } | ||
562 | - } | ||
563 | - | ||
564 | - Future<void> _share() async { | ||
565 | - // Calculate the widget center for iPad sharing popup position | ||
566 | - final referenceBox = | ||
567 | - shareWidget.currentContext!.findRenderObject() as RenderBox; | ||
568 | - final topLeft = | ||
569 | - referenceBox.localToGlobal(referenceBox.paintBounds.topLeft); | ||
570 | - final bottomRight = | ||
571 | - referenceBox.localToGlobal(referenceBox.paintBounds.bottomRight); | ||
572 | - final bounds = Rect.fromPoints(topLeft, bottomRight); | ||
573 | - | ||
574 | - final bytes = await widget.build(computedPageFormat); | ||
575 | - final result = await Printing.sharePdf( | ||
576 | - bytes: bytes, | ||
577 | - bounds: bounds, | ||
578 | - filename: widget.pdfFileName ?? 'document.pdf', | ||
579 | - body: widget.shareActionExtraBody, | ||
580 | - subject: widget.shareActionExtraSubject, | ||
581 | - emails: widget.shareActionExtraEmails, | ||
582 | - ); | ||
583 | - | ||
584 | - if (result && widget.onShared != null) { | ||
585 | - widget.onShared!(context); | ||
586 | - } | ||
587 | - } | ||
588 | } | 347 | } |
1 | -/* | ||
2 | - * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | - * | ||
4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | - * you may not use this file except in compliance with the License. | ||
6 | - * You may obtain a copy of the License at | ||
7 | - * | ||
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | - * | ||
10 | - * Unless required by applicable law or agreed to in writing, software | ||
11 | - * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | - * See the License for the specific language governing permissions and | ||
14 | - * limitations under the License. | ||
15 | - */ | ||
16 | - | ||
17 | -import 'package:flutter/material.dart'; | ||
18 | -import 'package:pdf/pdf.dart'; | ||
19 | - | ||
20 | -import '../callback.dart'; | ||
21 | - | ||
22 | -/// Base Action callback | ||
23 | -typedef OnPdfPreviewActionPressed = void Function( | ||
24 | - BuildContext context, | ||
25 | - LayoutCallback build, | ||
26 | - PdfPageFormat pageFormat, | ||
27 | -); | ||
28 | - | ||
29 | -/// Action to add the the [PdfPreview] widget | ||
30 | -class PdfPreviewAction { | ||
31 | - /// Represents an icon to add to [PdfPreview] | ||
32 | - const PdfPreviewAction({ | ||
33 | - required this.icon, | ||
34 | - required this.onPressed, | ||
35 | - }); | ||
36 | - | ||
37 | - /// The icon to display | ||
38 | - final Icon icon; | ||
39 | - | ||
40 | - /// The callback called when the user tap on the icon | ||
41 | - final OnPdfPreviewActionPressed? onPressed; | ||
42 | -} |
@@ -25,18 +25,15 @@ import 'package:pdf/pdf.dart'; | @@ -25,18 +25,15 @@ import 'package:pdf/pdf.dart'; | ||
25 | import '../printing.dart'; | 25 | import '../printing.dart'; |
26 | import '../printing_info.dart'; | 26 | import '../printing_info.dart'; |
27 | import '../raster.dart'; | 27 | import '../raster.dart'; |
28 | -import 'pdf_preview.dart'; | ||
29 | -import 'pdf_preview_page.dart'; | 28 | +import 'custom.dart'; |
29 | +import 'page.dart'; | ||
30 | 30 | ||
31 | /// Raster PDF documents | 31 | /// Raster PDF documents |
32 | -mixin PdfPreviewRaster on State<PdfPreview> { | 32 | +mixin PdfPreviewRaster on State<PdfPreviewCustom> { |
33 | static const _updateTime = Duration(milliseconds: 300); | 33 | static const _updateTime = Duration(milliseconds: 300); |
34 | 34 | ||
35 | /// Configured page format | 35 | /// Configured page format |
36 | - PdfPageFormat get pageFormat; | ||
37 | - | ||
38 | - /// Is the print horizontal | ||
39 | - bool? horizontal; | 36 | + PdfPageFormat get pageFormat => widget.pageFormat; |
40 | 37 | ||
41 | /// Resulting pages | 38 | /// Resulting pages |
42 | final List<PdfPreviewPage> pages = <PdfPreviewPage>[]; | 39 | final List<PdfPreviewPage> pages = <PdfPreviewPage>[]; |
@@ -60,11 +57,6 @@ mixin PdfPreviewRaster on State<PdfPreview> { | @@ -60,11 +57,6 @@ mixin PdfPreviewRaster on State<PdfPreview> { | ||
60 | super.dispose(); | 57 | super.dispose(); |
61 | } | 58 | } |
62 | 59 | ||
63 | - /// Computed page format | ||
64 | - PdfPageFormat get computedPageFormat => horizontal != null | ||
65 | - ? (horizontal! ? pageFormat.landscape : pageFormat.portrait) | ||
66 | - : pageFormat; | ||
67 | - | ||
68 | /// Rasterize the document | 60 | /// Rasterize the document |
69 | void raster() { | 61 | void raster() { |
70 | _previewUpdate?.cancel(); | 62 | _previewUpdate?.cancel(); |
@@ -72,7 +64,7 @@ mixin PdfPreviewRaster on State<PdfPreview> { | @@ -72,7 +64,7 @@ mixin PdfPreviewRaster on State<PdfPreview> { | ||
72 | final mq = MediaQuery.of(context); | 64 | final mq = MediaQuery.of(context); |
73 | dpi = (min(mq.size.width - 16, widget.maxPageWidth ?? double.infinity)) * | 65 | dpi = (min(mq.size.width - 16, widget.maxPageWidth ?? double.infinity)) * |
74 | mq.devicePixelRatio / | 66 | mq.devicePixelRatio / |
75 | - computedPageFormat.width * | 67 | + pageFormat.width * |
76 | 72; | 68 | 72; |
77 | 69 | ||
78 | _raster(); | 70 | _raster(); |
@@ -107,13 +99,13 @@ mixin PdfPreviewRaster on State<PdfPreview> { | @@ -107,13 +99,13 @@ mixin PdfPreviewRaster on State<PdfPreview> { | ||
107 | } | 99 | } |
108 | 100 | ||
109 | try { | 101 | try { |
110 | - _doc = await widget.build(computedPageFormat); | 102 | + _doc = await widget.build(pageFormat); |
111 | } catch (exception, stack) { | 103 | } catch (exception, stack) { |
112 | InformationCollector? collector; | 104 | InformationCollector? collector; |
113 | 105 | ||
114 | assert(() { | 106 | assert(() { |
115 | collector = () sync* { | 107 | collector = () sync* { |
116 | - yield StringProperty('PageFormat', computedPageFormat.toString()); | 108 | + yield StringProperty('PageFormat', pageFormat.toString()); |
117 | }; | 109 | }; |
118 | return true; | 110 | return true; |
119 | }()); | 111 | }()); |
@@ -174,7 +166,7 @@ mixin PdfPreviewRaster on State<PdfPreview> { | @@ -174,7 +166,7 @@ mixin PdfPreviewRaster on State<PdfPreview> { | ||
174 | 166 | ||
175 | assert(() { | 167 | assert(() { |
176 | collector = () sync* { | 168 | collector = () sync* { |
177 | - yield StringProperty('PageFormat', computedPageFormat.toString()); | 169 | + yield StringProperty('PageFormat', pageFormat.toString()); |
178 | }; | 170 | }; |
179 | return true; | 171 | return true; |
180 | }()); | 172 | }()); |
-
Please register or login to post a comment