Showing
6 changed files
with
200 additions
and
28 deletions
| @@ -24,6 +24,7 @@ export 'src/asset_utils.dart'; | @@ -24,6 +24,7 @@ export 'src/asset_utils.dart'; | ||
| 24 | export 'src/cache.dart'; | 24 | export 'src/cache.dart'; |
| 25 | export 'src/callback.dart'; | 25 | export 'src/callback.dart'; |
| 26 | export 'src/fonts/gfonts.dart'; | 26 | export 'src/fonts/gfonts.dart'; |
| 27 | +export 'src/preview/action_bar_theme.dart'; | ||
| 27 | export 'src/preview/actions.dart'; | 28 | export 'src/preview/actions.dart'; |
| 28 | export 'src/preview/pdf_preview.dart'; | 29 | export 'src/preview/pdf_preview.dart'; |
| 29 | export 'src/printer.dart'; | 30 | export 'src/printer.dart'; |
| 1 | +import 'package:flutter/foundation.dart'; | ||
| 2 | +import 'package:flutter/material.dart'; | ||
| 3 | + | ||
| 4 | +class PdfActionBarTheme with Diagnosticable { | ||
| 5 | + /// Creates a theme for action bar of [PdfPreviewController]. | ||
| 6 | + const PdfActionBarTheme({ | ||
| 7 | + this.backgroundColor, | ||
| 8 | + this.iconColor, | ||
| 9 | + this.height, | ||
| 10 | + this.textStyle, | ||
| 11 | + this.elevation = 4.0, | ||
| 12 | + this.actionSpacing = 0.0, | ||
| 13 | + this.alignment = WrapAlignment.spaceAround, | ||
| 14 | + this.runAlignment = WrapAlignment.center, | ||
| 15 | + this.crossAxisAlignment = WrapCrossAlignment.center, | ||
| 16 | + }); | ||
| 17 | + | ||
| 18 | + final Color? backgroundColor; | ||
| 19 | + final Color? iconColor; | ||
| 20 | + final double? height; | ||
| 21 | + final TextStyle? textStyle; | ||
| 22 | + final double elevation; | ||
| 23 | + final double actionSpacing; | ||
| 24 | + final WrapAlignment alignment; | ||
| 25 | + final WrapAlignment runAlignment; | ||
| 26 | + final WrapCrossAlignment crossAxisAlignment; | ||
| 27 | + | ||
| 28 | + /// Creates a copy of this object but with the given fields replaced with the | ||
| 29 | + /// new values. | ||
| 30 | + PdfActionBarTheme copyWith({ | ||
| 31 | + Color? backgroundColor, | ||
| 32 | + Color? iconColor, | ||
| 33 | + double? height, | ||
| 34 | + TextStyle? textStyle, | ||
| 35 | + double? elevation, | ||
| 36 | + double? actionSpacing, | ||
| 37 | + WrapAlignment? alignment, | ||
| 38 | + WrapAlignment? runAlignment, | ||
| 39 | + WrapCrossAlignment? crossAxisAlignment, | ||
| 40 | + }) { | ||
| 41 | + return PdfActionBarTheme( | ||
| 42 | + backgroundColor: backgroundColor ?? this.backgroundColor, | ||
| 43 | + iconColor: iconColor ?? this.iconColor, | ||
| 44 | + height: height ?? this.height, | ||
| 45 | + textStyle: textStyle ?? this.textStyle, | ||
| 46 | + elevation: elevation ?? this.elevation, | ||
| 47 | + actionSpacing: actionSpacing ?? this.actionSpacing, | ||
| 48 | + alignment: alignment ?? this.alignment, | ||
| 49 | + runAlignment: runAlignment ?? this.runAlignment, | ||
| 50 | + crossAxisAlignment: crossAxisAlignment ?? this.crossAxisAlignment, | ||
| 51 | + ); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + @override | ||
| 55 | + int get hashCode => Object.hashAll([ | ||
| 56 | + backgroundColor, | ||
| 57 | + iconColor, | ||
| 58 | + height, | ||
| 59 | + textStyle, | ||
| 60 | + elevation, | ||
| 61 | + actionSpacing, | ||
| 62 | + alignment, | ||
| 63 | + runAlignment, | ||
| 64 | + crossAxisAlignment, | ||
| 65 | + ]); | ||
| 66 | + | ||
| 67 | + @override | ||
| 68 | + bool operator ==(Object other) { | ||
| 69 | + if (identical(this, other)) { | ||
| 70 | + return true; | ||
| 71 | + } | ||
| 72 | + if (other.runtimeType != runtimeType) { | ||
| 73 | + return false; | ||
| 74 | + } | ||
| 75 | + return other is PdfActionBarTheme && | ||
| 76 | + other.backgroundColor == backgroundColor && | ||
| 77 | + other.iconColor == iconColor && | ||
| 78 | + other.height == height && | ||
| 79 | + other.textStyle == textStyle && | ||
| 80 | + other.elevation == elevation && | ||
| 81 | + other.actionSpacing == actionSpacing && | ||
| 82 | + other.alignment == alignment && | ||
| 83 | + other.runAlignment == runAlignment && | ||
| 84 | + other.crossAxisAlignment == crossAxisAlignment; | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + @override | ||
| 88 | + void debugFillProperties(DiagnosticPropertiesBuilder properties) { | ||
| 89 | + super.debugFillProperties(properties); | ||
| 90 | + properties.add(ColorProperty('backgroundColor', backgroundColor)); | ||
| 91 | + properties.add(ColorProperty('iconColor', iconColor)); | ||
| 92 | + properties.add(DoubleProperty('height', height)); | ||
| 93 | + properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle)); | ||
| 94 | + properties.add(DoubleProperty('elevation', elevation)); | ||
| 95 | + properties.add(DoubleProperty('actionSpacing', actionSpacing)); | ||
| 96 | + properties.add(DiagnosticsProperty<WrapAlignment>('alignment', alignment, | ||
| 97 | + defaultValue: WrapAlignment.spaceAround)); | ||
| 98 | + properties.add(DiagnosticsProperty<WrapAlignment>( | ||
| 99 | + 'runAlignment', runAlignment, | ||
| 100 | + defaultValue: WrapAlignment.center)); | ||
| 101 | + properties.add(DiagnosticsProperty<WrapCrossAlignment>( | ||
| 102 | + 'crossAxisAlignment', crossAxisAlignment, | ||
| 103 | + defaultValue: WrapCrossAlignment.center)); | ||
| 104 | + } | ||
| 105 | +} |
| @@ -50,6 +50,7 @@ class PdfPreviewCustom extends StatefulWidget { | @@ -50,6 +50,7 @@ class PdfPreviewCustom extends StatefulWidget { | ||
| 50 | this.scrollPhysics, | 50 | this.scrollPhysics, |
| 51 | this.shrinkWrap = false, | 51 | this.shrinkWrap = false, |
| 52 | this.pagesBuilder, | 52 | this.pagesBuilder, |
| 53 | + this.enableScrollToPage = false, | ||
| 53 | }) : super(key: key); | 54 | }) : super(key: key); |
| 54 | 55 | ||
| 55 | /// Pdf paper page format | 56 | /// Pdf paper page format |
| @@ -102,6 +103,9 @@ class PdfPreviewCustom extends StatefulWidget { | @@ -102,6 +103,9 @@ class PdfPreviewCustom extends StatefulWidget { | ||
| 102 | /// their own pages. | 103 | /// their own pages. |
| 103 | final CustomPdfPagesBuilder? pagesBuilder; | 104 | final CustomPdfPagesBuilder? pagesBuilder; |
| 104 | 105 | ||
| 106 | + /// Whether scroll to page functionality enabled. | ||
| 107 | + final bool enableScrollToPage; | ||
| 108 | + | ||
| 105 | @override | 109 | @override |
| 106 | PdfPreviewCustomState createState() => PdfPreviewCustomState(); | 110 | PdfPreviewCustomState createState() => PdfPreviewCustomState(); |
| 107 | } | 111 | } |
| @@ -110,6 +114,8 @@ class PdfPreviewCustomState extends State<PdfPreviewCustom> | @@ -110,6 +114,8 @@ class PdfPreviewCustomState extends State<PdfPreviewCustom> | ||
| 110 | with PdfPreviewRaster { | 114 | with PdfPreviewRaster { |
| 111 | final listView = GlobalKey(); | 115 | final listView = GlobalKey(); |
| 112 | 116 | ||
| 117 | + List<GlobalKey> _pageGlobalKeys = <GlobalKey>[]; | ||
| 118 | + | ||
| 113 | bool infoLoaded = false; | 119 | bool infoLoaded = false; |
| 114 | 120 | ||
| 115 | int? preview; | 121 | int? preview; |
| @@ -172,6 +178,24 @@ class PdfPreviewCustomState extends State<PdfPreviewCustom> | @@ -172,6 +178,24 @@ class PdfPreviewCustomState extends State<PdfPreviewCustom> | ||
| 172 | super.didChangeDependencies(); | 178 | super.didChangeDependencies(); |
| 173 | } | 179 | } |
| 174 | 180 | ||
| 181 | + /// Ensures that page with [index] is become visible. | ||
| 182 | + Future<void> scrollToPage( | ||
| 183 | + int index, { | ||
| 184 | + Duration duration = const Duration(milliseconds: 300), | ||
| 185 | + Curve curve = Curves.ease, | ||
| 186 | + ScrollPositionAlignmentPolicy alignmentPolicy = | ||
| 187 | + ScrollPositionAlignmentPolicy.explicit, | ||
| 188 | + }) { | ||
| 189 | + assert(index >= 0, 'Index of page cannot be negative'); | ||
| 190 | + final pageContext = _pageGlobalKeys[index].currentContext; | ||
| 191 | + assert(pageContext != null, 'Context of GlobalKey cannot be null'); | ||
| 192 | + return Scrollable.ensureVisible(pageContext!, | ||
| 193 | + duration: duration, curve: curve, alignmentPolicy: alignmentPolicy); | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + /// Returns the global key for page with [index]. | ||
| 197 | + Key getPageKey(int index) => _pageGlobalKeys[index]; | ||
| 198 | + | ||
| 175 | Widget _showError(Object error) { | 199 | Widget _showError(Object error) { |
| 176 | if (widget.onError != null) { | 200 | if (widget.onError != null) { |
| 177 | return widget.onError!(context, error); | 201 | return widget.onError!(context, error); |
| @@ -197,30 +221,53 @@ class PdfPreviewCustomState extends State<PdfPreviewCustom> | @@ -197,30 +221,53 @@ class PdfPreviewCustomState extends State<PdfPreviewCustom> | ||
| 197 | ); | 221 | ); |
| 198 | } | 222 | } |
| 199 | 223 | ||
| 224 | + if (widget.enableScrollToPage) { | ||
| 225 | + _pageGlobalKeys = List.generate(pages.length, (_) => GlobalKey()); | ||
| 226 | + } | ||
| 227 | + | ||
| 200 | if (widget.pagesBuilder != null) { | 228 | if (widget.pagesBuilder != null) { |
| 201 | return widget.pagesBuilder!(context, pages); | 229 | return widget.pagesBuilder!(context, pages); |
| 202 | } | 230 | } |
| 203 | - return ListView.builder( | ||
| 204 | - controller: scrollController, | ||
| 205 | - shrinkWrap: widget.shrinkWrap, | ||
| 206 | - physics: widget.scrollPhysics, | ||
| 207 | - padding: widget.padding, | ||
| 208 | - itemCount: pages.length, | ||
| 209 | - itemBuilder: (BuildContext context, int index) => GestureDetector( | ||
| 210 | - onDoubleTap: () { | ||
| 211 | - setState(() { | ||
| 212 | - updatePosition = scrollController.position.pixels; | ||
| 213 | - preview = index; | ||
| 214 | - transformationController.value.setIdentity(); | ||
| 215 | - }); | ||
| 216 | - }, | ||
| 217 | - child: PdfPreviewPage( | ||
| 218 | - pageData: pages[index], | ||
| 219 | - pdfPreviewPageDecoration: widget.pdfPreviewPageDecoration, | ||
| 220 | - pageMargin: widget.previewPageMargin, | ||
| 221 | - ), | ||
| 222 | - ), | ||
| 223 | - ); | 231 | + |
| 232 | + Widget pageWidget(int index, {Key? key}) => GestureDetector( | ||
| 233 | + onDoubleTap: () { | ||
| 234 | + setState(() { | ||
| 235 | + updatePosition = scrollController.position.pixels; | ||
| 236 | + preview = index; | ||
| 237 | + transformationController.value.setIdentity(); | ||
| 238 | + }); | ||
| 239 | + }, | ||
| 240 | + child: PdfPreviewPage( | ||
| 241 | + key: key, | ||
| 242 | + pageData: pages[index], | ||
| 243 | + pdfPreviewPageDecoration: widget.pdfPreviewPageDecoration, | ||
| 244 | + pageMargin: widget.previewPageMargin, | ||
| 245 | + ), | ||
| 246 | + ); | ||
| 247 | + | ||
| 248 | + return widget.enableScrollToPage | ||
| 249 | + ? Scrollbar( | ||
| 250 | + controller: scrollController, | ||
| 251 | + child: SingleChildScrollView( | ||
| 252 | + controller: scrollController, | ||
| 253 | + physics: widget.scrollPhysics, | ||
| 254 | + padding: widget.padding, | ||
| 255 | + child: Column( | ||
| 256 | + children: List.generate( | ||
| 257 | + pages.length, | ||
| 258 | + (index) => pageWidget(index, key: getPageKey(index)), | ||
| 259 | + ), | ||
| 260 | + ), | ||
| 261 | + ), | ||
| 262 | + ) | ||
| 263 | + : ListView.builder( | ||
| 264 | + controller: scrollController, | ||
| 265 | + shrinkWrap: widget.shrinkWrap, | ||
| 266 | + physics: widget.scrollPhysics, | ||
| 267 | + padding: widget.padding, | ||
| 268 | + itemCount: pages.length, | ||
| 269 | + itemBuilder: (BuildContext context, int index) => pageWidget(index), | ||
| 270 | + ); | ||
| 224 | } | 271 | } |
| 225 | 272 | ||
| 226 | Widget _zoomPreview() { | 273 | Widget _zoomPreview() { |
| @@ -21,6 +21,7 @@ import 'package:pdf/widgets.dart' as pw; | @@ -21,6 +21,7 @@ import 'package:pdf/widgets.dart' as pw; | ||
| 21 | import '../callback.dart'; | 21 | import '../callback.dart'; |
| 22 | import '../printing.dart'; | 22 | import '../printing.dart'; |
| 23 | import '../printing_info.dart'; | 23 | import '../printing_info.dart'; |
| 24 | +import 'action_bar_theme.dart'; | ||
| 24 | import 'actions.dart'; | 25 | import 'actions.dart'; |
| 25 | import 'controller.dart'; | 26 | import 'controller.dart'; |
| 26 | import 'custom.dart'; | 27 | import 'custom.dart'; |
| @@ -62,6 +63,8 @@ class PdfPreview extends StatefulWidget { | @@ -62,6 +63,8 @@ class PdfPreview extends StatefulWidget { | ||
| 62 | this.loadingWidget, | 63 | this.loadingWidget, |
| 63 | this.onPageFormatChanged, | 64 | this.onPageFormatChanged, |
| 64 | this.dpi, | 65 | this.dpi, |
| 66 | + this.actionBarTheme = const PdfActionBarTheme(), | ||
| 67 | + this.enableScrollToPage = false, | ||
| 65 | }) : _pagesBuilder = null, | 68 | }) : _pagesBuilder = null, |
| 66 | super(key: key); | 69 | super(key: key); |
| 67 | 70 | ||
| @@ -119,7 +122,9 @@ class PdfPreview extends StatefulWidget { | @@ -119,7 +122,9 @@ class PdfPreview extends StatefulWidget { | ||
| 119 | this.loadingWidget, | 122 | this.loadingWidget, |
| 120 | this.onPageFormatChanged, | 123 | this.onPageFormatChanged, |
| 121 | this.dpi, | 124 | this.dpi, |
| 125 | + this.actionBarTheme = const PdfActionBarTheme(), | ||
| 122 | required CustomPdfPagesBuilder pagesBuilder, | 126 | required CustomPdfPagesBuilder pagesBuilder, |
| 127 | + this.enableScrollToPage = false, | ||
| 123 | }) : _pagesBuilder = pagesBuilder, | 128 | }) : _pagesBuilder = pagesBuilder, |
| 124 | super(key: key); | 129 | super(key: key); |
| 125 | 130 | ||
| @@ -223,10 +228,16 @@ class PdfPreview extends StatefulWidget { | @@ -223,10 +228,16 @@ class PdfPreview extends StatefulWidget { | ||
| 223 | /// If not provided, this value is calculated. | 228 | /// If not provided, this value is calculated. |
| 224 | final double? dpi; | 229 | final double? dpi; |
| 225 | 230 | ||
| 231 | + /// The style of actions bar. | ||
| 232 | + final PdfActionBarTheme actionBarTheme; | ||
| 233 | + | ||
| 226 | /// clients can pass this builder to render | 234 | /// clients can pass this builder to render |
| 227 | /// their own pages. | 235 | /// their own pages. |
| 228 | final CustomPdfPagesBuilder? _pagesBuilder; | 236 | final CustomPdfPagesBuilder? _pagesBuilder; |
| 229 | 237 | ||
| 238 | + /// Whether scroll to page functionality enabled. | ||
| 239 | + final bool enableScrollToPage; | ||
| 240 | + | ||
| 230 | @override | 241 | @override |
| 231 | PdfPreviewState createState() => PdfPreviewState(); | 242 | PdfPreviewState createState() => PdfPreviewState(); |
| 232 | } | 243 | } |
| @@ -296,7 +307,6 @@ class PdfPreviewState extends State<PdfPreview> { | @@ -296,7 +307,6 @@ class PdfPreviewState extends State<PdfPreview> { | ||
| 296 | initialPageFormat: previewData.pageFormat, | 307 | initialPageFormat: previewData.pageFormat, |
| 297 | onComputeActualPageFormat: computeActualPageFormat, | 308 | onComputeActualPageFormat: computeActualPageFormat, |
| 298 | ); | 309 | ); |
| 299 | - setState(() {}); | ||
| 300 | } | 310 | } |
| 301 | super.didUpdateWidget(oldWidget); | 311 | super.didUpdateWidget(oldWidget); |
| 302 | } | 312 | } |
| @@ -400,22 +410,30 @@ class PdfPreviewState extends State<PdfPreview> { | @@ -400,22 +410,30 @@ class PdfPreviewState extends State<PdfPreview> { | ||
| 400 | shouldRepaint: widget.shouldRepaint, | 410 | shouldRepaint: widget.shouldRepaint, |
| 401 | pagesBuilder: widget._pagesBuilder, | 411 | pagesBuilder: widget._pagesBuilder, |
| 402 | dpi: widget.dpi, | 412 | dpi: widget.dpi, |
| 413 | + enableScrollToPage: widget.enableScrollToPage, | ||
| 403 | ); | 414 | ); |
| 404 | }), | 415 | }), |
| 405 | ), | 416 | ), |
| 406 | if (actions.isNotEmpty) | 417 | if (actions.isNotEmpty) |
| 407 | IconTheme.merge( | 418 | IconTheme.merge( |
| 408 | data: IconThemeData( | 419 | data: IconThemeData( |
| 409 | - color: iconColor, | 420 | + color: widget.actionBarTheme.iconColor ?? iconColor, |
| 410 | ), | 421 | ), |
| 411 | child: Material( | 422 | child: Material( |
| 412 | - elevation: 4, | ||
| 413 | - color: theme.primaryColor, | 423 | + elevation: widget.actionBarTheme.elevation, |
| 424 | + color: | ||
| 425 | + widget.actionBarTheme.backgroundColor ?? theme.primaryColor, | ||
| 426 | + textStyle: widget.actionBarTheme.textStyle, | ||
| 414 | child: SizedBox( | 427 | child: SizedBox( |
| 415 | width: double.infinity, | 428 | width: double.infinity, |
| 429 | + height: widget.actionBarTheme.height, | ||
| 416 | child: SafeArea( | 430 | child: SafeArea( |
| 417 | child: Wrap( | 431 | child: Wrap( |
| 418 | - alignment: WrapAlignment.spaceAround, | 432 | + spacing: widget.actionBarTheme.actionSpacing, |
| 433 | + alignment: widget.actionBarTheme.alignment, | ||
| 434 | + runAlignment: widget.actionBarTheme.runAlignment, | ||
| 435 | + crossAxisAlignment: | ||
| 436 | + widget.actionBarTheme.crossAxisAlignment, | ||
| 419 | children: actions, | 437 | children: actions, |
| 420 | ), | 438 | ), |
| 421 | ), | 439 | ), |
-
Please register or login to post a comment