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